Back to portfolio

React app / TDD / Playwright / product thinking

Type Type

Type Type is a typing test platform designed to make typing practice feel fast, focused, and measurable. The project looks simple from the outside, but the core challenge was building an interaction-heavy app where every keystroke changes state, feedback, progress, and results.

I approached it as both a product and engineering exercise: define the smallest useful practice loop, protect the fragile behavior with tests, and structure the app so future features like multiplayer and richer statistics can be added without rewriting the foundation.

Type Type typing test platform screenshot

Role

Frontend developer responsible for product flow, React implementation, state handling, test strategy, and UI iteration.

Stack

React, JavaScript, TDD, Playwright, responsive UI patterns.

Product Goal

Help users practice typing with immediate feedback, clear progress, and a simple experience that does not distract from the test itself.

Project Snapshot

01

Interaction-first product

The main value is not a static page. It is the accuracy and feel of the typing loop.

02

Test-protected behavior

The riskiest parts of the app were protected with TDD and end-to-end validation.

03

Built for iteration

The foundation was kept simple enough to ship, but structured for richer stats and multiplayer later.

Why This Project

I wanted to build a project where the main value did not come from a static layout, but from the quality of the interaction. A typing test is a good challenge because the app has to respond instantly and correctly to every input. Small bugs are easy for users to feel: incorrect character highlighting, timer issues, result mismatches, or state that does not reset cleanly can break trust immediately.

That made Type Type a useful project for practicing product thinking, state design, testing discipline, and frontend interaction details in one focused experience.

The product question was: how do we make a tiny interaction feel reliable enough that users can trust it over and over again?

The Problem

Typing practice apps appear straightforward, but the core flow is state-heavy. The app needs to know what the user is typing, what text they are typing against, which character is active, which characters are correct or incorrect, when the test starts, when it ends, and what results should be shown.

The challenge was to keep that logic reliable while keeping the user interface minimal. I did not want users to think about the app. I wanted them to start typing, understand their mistakes, and finish with useful feedback.

User sees

Prompt text, current character, mistake feedback, and result.

App tracks

Input, expected text, active index, accuracy, time, and status.

Risk

One incorrect state update can make the entire test feel broken.

Core User Flow

Start

User enters the test without extra setup.

Type

Every keystroke updates progress and feedback.

Validate

The app compares typed input with expected characters.

Result

User sees completion feedback and can try again.

1. Enter the test quickly

The first screen needed to make the action obvious. The user should not need onboarding or explanation before starting a typing session.

2. Type with live feedback

The main interaction had to show progress and mistakes without visually overwhelming the user. Feedback needed to be visible but not noisy.

3. Finish with clear results

At the end of a test, the app should help the user understand how they performed and encourage another attempt.

Product Decisions

I made product decisions by asking whether each idea helped the user type, understand feedback, or improve over repeated attempts. Anything outside that loop was treated as secondary.

Keep the interface focused

Why

I avoided adding too many controls or decorative elements because the typing area is the product. The UI should support the practice session, not compete with it.

Impact

Users can understand the task immediately and stay in flow.

Prioritize instant feedback

Why

Users need to know whether they are on track while typing. Live character feedback is more useful than waiting until the end to show errors.

Impact

Mistakes become easier to notice and correct during practice.

Design for repetition

Why

Typing practice is repetitive by nature, so resetting the test, starting again, and understanding the result state needed to feel smooth.

Impact

The product feels like a practice tool, not a one-time demo.

Engineering Approach

I treated the typing experience as a small state machine. Instead of thinking only in terms of screens, I thought through the states the app can be in: waiting to start, actively typing, showing mistakes, completing the test, and resetting for another round.

This helped make the logic easier to reason about. When the user types a character, the app has to compare it against the expected character, update progress, update feedback, and decide whether the test is complete. Separating those decisions from the visual rendering made the app easier to test and change.

Input

Keyboard events and typed characters

Logic

Comparison, active index, accuracy, completion

State

Current progress, mistake status, test lifecycle

UI

Prompt display, feedback, progress, result state

Tests

Unit behavior, component behavior, end-to-end flow

Typing Session State Model

A useful way to think about the app was as a session with clear states. This made the behavior easier to reason about and helped define what should be tested.

Idle

Prompt is visible, input is empty, and the timer has not started.

Active

User is typing, feedback updates, and progress is tracked.

Completed

The prompt is finished and the app shows the result state.

Reset

Input and result state are cleared so the user can try again.

Why TDD

I used test-driven development because the highest-risk part of the project was not how the UI looked. It was whether the interaction behaved correctly across many small input changes.

The TDD approach also helped me slow down at the right moment. Before writing UI code, I had to define what correct behavior actually meant: what should happen after a correct key, an incorrect key, a completed prompt, or a reset. That made the implementation more intentional.

Testing Strategy

The testing strategy followed the same shape as the product risk. The closer something was to the typing interaction, the more important it was to verify.

Unit-level confidence

Core typing behavior was treated as logic that should be tested directly: character matching, progress, completion, and reset behavior.

Component behavior

UI behavior needed to reflect the underlying state clearly. The component layer was tested around what users can see and do.

End-to-end validation

Playwright was used to validate the complete flow like a real user: open the app, type through a test, and reach the expected result.

Examples of Test Cases

I focused test coverage on behavior that would immediately affect trust in the product if it broke.

Typing behavior

  • Starts the test when the user begins typing.
  • Marks correct characters correctly.
  • Marks incorrect characters correctly.
  • Tracks progress through the prompt.

Session behavior

  • Does not show results before completion.
  • Ends the test when the prompt is completed.
  • Clears input and result state after retry.
  • Keeps reset behavior predictable.

End-to-end flow

  • Loads the app successfully.
  • Allows a user to complete a full typing session.
  • Shows the expected final result state.

Why Playwright

For this type of app, end-to-end tests are valuable because the main risk is the full interaction, not one isolated function. Playwright helped verify that keyboard input, DOM updates, visual states, and test completion worked together.

This gave me confidence that the app still worked after UI changes, styling changes, or refactors to the typing logic.

Edge Cases Considered

Because typing apps react to rapid input, edge cases can appear very quickly. I considered these scenarios while shaping the logic and test coverage.

UI and Interaction Decisions

Visually, I wanted the page to feel direct and low-friction. The design should support concentration: clear text, obvious feedback, and enough whitespace to make the typing area feel calm.

Tradeoffs

The biggest tradeoff was choosing depth over breadth. It would have been easy to add more visible features, but the project was stronger when the core interaction was treated as the main feature.

Simple first, expandable later

I kept the first version focused on the core typing experience instead of adding many modes immediately. This made the foundation easier to validate.

Testing important paths first

Rather than trying to test everything, I focused testing effort on the paths most likely to break the user experience: typing, feedback, completion, and reset.

Product scope

Multiplayer was planned as a future direction, but I did not want to add it before the single-player typing loop felt solid.

Constraints

What I Learned

Type Type reinforced that interactive frontend work is not only about components and styling. The quality of the experience depends on how well product flow, state design, and testing work together.

It also showed the value of keeping a project small enough to finish but deep enough to exercise real engineering decisions.

Future Improvements

What This Project Shows

Type Type shows how I approach frontend engineering beyond visual implementation. I think through the user loop, identify fragile behavior, model state clearly, test the parts that matter, and keep the interface focused on the user's task.

It also reflects the kind of work I enjoy: product-minded frontend development where interaction quality, maintainable code, and user experience all matter at the same time.

Outcome

The result is a working typing practice platform with a stronger engineering foundation than a simple demo. It shows how I approach an interaction-heavy product: understand the user loop, make deliberate product decisions, isolate risky logic, test important behavior, and keep the UI focused on the task.