How I built the Scorecard.dev frontend

The purpose of this site is to show my thought process when designing and building applications. If you would like to see the frontend source code that was used to create Scorecard.dev, it is available on GitHub.


Starting with a blueprint

The most common mistake I see developers making when trying to get their product to market is not creating a diagram representing the final product before they begin coding. Borrowing the term from the construction industry, I call these comprehensive diagrams "blueprints." When you want to build your user interface around reusable components, working from a plan will make it easier for you to identify opportunities for reuse. These reuse opportunities tend to be far harder to spot when you don't have an over-arching vision for how the final app will look.

The opening screens as they looked in the Blueprint
The opening screens as they looked in the Blueprint

If you take a look at the blueprint I created, you'll notice that while its not the same as the final app, the diagram represents all of the major concepts. Many developers think a blueprint has to be pixel perfect, but achieving that level of perfection is usually not necessary, as you can adjust with CSS in a second what might take someone an hour to change in a drawing program (I used Visio). The key is to understand the app holistically so that any surprises you run into will be minor.

The opening screens as they looked in the final production version
The opening screens as they looked in the final production version

I worked on the blueprint as I was recording and editing podcasts, and on average, I probably spent about an hour working on it every day for around a month. This period is also where I made significant technical design decisions so that when the time would come to code, I would only have to focus on the implementation, not the overarching system architecture.

Throughout the coding process, I was constantly referring back to the blueprint to remind myself of where I was and what I was trying to achieve at each stage of development. It is common to get lost in the weeds after several thousand lines of code, and occasionally you'll need to come up for air and reorient yourself by studying the blueprint.

Choosing technologies

I picked the technologies of React, Redux, and CoffeeScript over contemporary alternatives. Here was my rationale:

I made sure the stack I chose was a close fit for what I needed, but if I were building a different style of app, I may have chosen another type of stack. For example, if I was making an app for a large enterprise that was to be maintained by hundreds of developers, I might have chosen ExtJS and Java Spring. The job should always dictate the tools, but if you don't know how to use something and have a short timeframe to get something finished, stick with what you know. It's not the most exciting advice in the world, but it's the most pragmatic if your priority is completion.

Designing the Component hierarchy

While I do start with a blueprint, I don't typically begin coding with a fixed plan for implementing specific components. Instead, two concepts guide me towards the final component hierarchy:

Component breakdown for the developer scorecard
Component breakdown for the developer scorecard

I reverse engineered a diagram of what the component hierarchy looked like for the sake of this article but bear in mind that I had no plan when I started. You should be able to tell where exactly I use each component by referring back to the blueprint.

The component hierarchy for Scorecard.dev
The component hierarchy for Scorecard.dev

Implementing a Flux-style architecture with Redux

Flux style architectures tend to be difficult for developers to wrap their heads around. The trouble is not the basic idea (a single state for the entire app), it's when you get into specific scenarios that people's head starts to spin. Ultimately, Flux-style architectures are very different than other user interface data models, and at times are incredibly counter-intuitive, and often seem to have no apparent benefit.

Flux-style architectures, as well as how they can or should interact with different styles of backend APIs, is a sprawling topic, so in the name of brevity I've made a diagram showing state management for the most sophisticated component in Scorecard.dev, which is the custom audio player. The audio player is a sextuple state-machine, which each state serving a specific purpose:

  1. Server - The server determines what options in the player are enabled based on: if the user has taken the quiz; if they have created an account; and if they have unlocked all results.
  2. Redux - Data from the server is combined with static data on the client to form the final set of properties used to initialize the react component.
  3. React Component - For performance reasons, ScorecardPlayer needed to manage part of its state outside of Redux to avoid over-rendering in high-event rate scenarios like sliding the player position.
  4. Audio Element - The HTML5 audio media element has an internal state which has to be kept synchronized with the state of the component.
  5. LocalStorage - For network performance reasons, localStorage is used to remember the state of the UI, as attempting to save player position on the server would create a lot of network traffic, and would still potentially not capture the state precisely if the user unloaded the page between server updates.
  6. Slider - Much like the Audio Element, the 3rd party slider has an internal state that has to be kept synchronized with the state of the component.
Having six states to synchronize is not typical and unless you have very specific reasons not advisable
Having six states to synchronize is not typical and unless you have very specific reasons not advisable

For your sanity, your application should have a limited number of state representations. If you use a JSON store such as Mongo, a REST API, Redux, and stateless React components, you should be able to have only one state if the data in Mongo is structured precisely as the React component hierarchy expects. Suffice to say, having six states to keep synchronized is far more complicated than only having one.

A custom routing system

Typically React apps use React Router for navigation, but I elected to create a custom system, as I needed more precise control over what happens when the user navigates. I also wanted tighter encapsulation over how the system behaves when the state changes the URL, or vice versa, which I achieved using a Redux Enhancer. Finally, I wanted the concern of routing to be as decoupled from the components as possible, which is not a primary concern of the latest version of React Router. Having said that, if you have a relatively straightforward relationship between the URL and the components and state, React Router will be dramatically more manageable than writing a custom solution.

Mobile-first responsive design

I decided to make a mobile-friendly interface early into the blueprinting phase, as I anticipated people would want to listen to the career advice podcasts while they were at their desktops as well as on their mobile devices. As mobile interfaces can work on desktops, but desktop interfaces usually do not work on mobile, focusing on a mobile-first design made sure I covered all possible devices. The consequence of this decision is that I would not be able to take full advantage of all of the available space at tablet/laptop/desktop widths, but while this would leave unused space on either side of the interface, at least users would only have to learn a single interface no matter what device they were on.

Scorecard.dev on an iPhone SE, Pixel 2 XL, and iPad
Scorecard.dev on an iPhone SE, Pixel 2 XL, and iPad

In practice, having a mobile-friendly interface at tablet/laptop/desktop widths meant that beyond a certain width, the interface would lock itself to a fixed-width, emulating a mobile interface on a desktop. Achieving this effect required a very light touch with responsive CSS: Below a specified width, the interface would switch from fixed-width to 100% width. While this is a single line of width-overriding CSS conditionally applied to a root container using a media query, it had the far-reaching implication that every UI element in the interface had to be designed to look and work correctly no matter what the width.

The Scorecard podcast player hides the volume controls at mobile widths
The Scorecard podcast player hides the volume controls at mobile widths

Applying CSS effects

I wanted to give Scorecard.dev a modern look loosely inspired by Material Design. I could have used the excellent Material UI React Component library but preferred to build custom components and styles so that I would have full control over both the look and the feel of the app.

Developers new to CSS effects tend to want to demonstrate what they know, and their enthusiasm often leads to app designs that have so many noticeable visual effects that the usability of the app suffers. The key to using CSS effects is subtly; it should not be apparent to the user that you are using CSS effects at all. A user should only be able to detect that you used visual effects if you take them away.

The Scorecard.dev interface with and without CSS effects
The Scorecard.dev interface with and without CSS effects

Visual effects should be complementary and work in harmony with each other. The objective is to arrive at a single, cohesive design where everything is tied together seamlessly. Unfortunately, CSS syntax makes this difficult, as there are no higher-level concepts like “lighting,” “depth,” or “contrast.” Instead, you have to work backward from the design you want into the CSS syntax you need.

A surprising amount of CSS effects were used for such a seemingly simple, clean design
A surprising amount of CSS effects were used for such a seemingly simple, clean design

Making circles connected to lines

I wanted to carry the theme of circles and lines throughout the app, to tie together the homepage, quiz, and Scorecard.

The general steps to creating a circle with a line attached in CSS are:

  1. Give an element equal height and width, a border, and a 50% border-radius.
  2. Create an element with a width and zero height (for horizontal lines) or a height with zero width (for vertical lines) and a border.
  3. Align the circle with the line to make them appear to be connected.
The structure of 1) The “1, 2, 3” on the homepage 2) The bars on the scorecard and 3) The quiz answer interface
The structure of 1) The “1, 2, 3” on the homepage 2) The bars on the scorecard and 3) The quiz answer interface

Specifically for Scorecard.dev, here is how I created each of the circles-with-lines effects:

  1. For the vertical “1, 2, 3” on the homepage, the challenge was making sure the circles were always in the vertical center of their associated paragraph of text no matter how much vertical height the text occupied. I used Flexbox to distribute the circle element and two pseudo elements within a parent container whose height was determined by the paragraph of text. Visually, this created the illusion of three circles connected by two lines, even though there were six lines in total. The very first and very last lines then had their visibility hidden so that they were invisible to the user but still rendered into the document so that Flexbox could still maintain proper alignment.
  2. For the horizontal score-bars on the scorecards, the zero height lines contain the ending circles. The ending circles use absolute positioning so that they do not increase the height of the line, and can be positioned precisely in the vertical and horizontal center of the end of the line.
  3. For the quiz answer selection, the circles and lines are thirteen sibling elements, and Flexbox is used to give them all the same vertical alignment despite them having different heights.

CSS transitions on scorecard bars

There were two situations where I wanted the bars on a scorecard to be animated:

  1. When switching between the twelve “Difficult Developer” profiles
  2. When loading a scorecard

Animating the scrollbars required only CSS transitions, not animations, as I was only animating between two states. The terminology can be a be confusing as you can create an animation using both a “CSS transition” and a “CSS animation”, with the difference between the techniques being the sophistication of the animation you wish to produce: simple animations need only CSS transitions; sophisticated animations require CSS animations.

Animation effect triggered when switching between developer archetypes
Animation effect triggered when switching between developer archetypes

To achieve the effect I wanted, I needed to transition the colors and width of the score-bar. The line of the bar only needed its border color and width transitioned; the end of a score bar had a circle effect that required the transition of the outer circle’s border as well as the inner circle’s background color. Achieving this effect was conceptually very simple, but I had two options of how I could code it:

While CSS classes allowed for a clean separation of visual effect from the React component, the drawback of creating CSS classes is I would need a lot of them: 100 to be exact as the score-bar represented score as a percentage. The side effect would be an increase in the CSS download size, and it was technically not needed as the transition would work just as well if I manipulated the style directly on the element. The difference between triggering the animation between the two effects was only very slight:

I decided to use CSS classes for the following reasons:

  1. While there is quite a lot of debate around coupling style to components, I believe that style should be separate from content in order to retain the option for a wholesale redesign of the app many years into the future.
  2. By the very nature of the CSS class repetitiveness, when compressed by the server, gzip tokenization would dramatically reduce the download size of the 100 class definitions.

The 2nd point was only an assumption at the time I made the decision, but for the sake of this article, I ran a test to see if my assumption was correct. The result was that the SASS loop created 23KB of CSS, which gzipped down to 1KB. Is 1KB of download worth separating style from implementation? Considering that the entire CSS bundle is 8KB compressed it’s a significant increase proportionally, but the practical reality is that on a typical connection the extra KB would be transferred in a few milliseconds. If I were designing the interface for Amazon’s level of traffic, I would have elected to save the 1KB. As it stands, the extra network transfer cost for the anticipated peak traffic for Scorecard.dev would amount to the cost a cup of coffee a month.

The SASS code that generated the 100 classes used for the score-bar transitions
The SASS code that generated the 100 classes used for the score-bar transitions

Dark Mode

Early in the design process, the interface for Scorecard.dev had a dark color scheme. An early reviewer suggested a change to a more corporate color scheme, but I resisted as I preferred the look of an IDE/Editor more than that of a corporate training solution. Ultimately, near the end of the blueprinting process, I decided that the reviewer was correct and switched from a dark color scheme to a light color scheme.

About a week after I launched Scorecard.dev I learned that the new version of iOS was implementing a Dark Mode, and immediately regretted using a light color scheme. After some reflection, I realized that what I wanted was a color scheme that reflected the preference of the user, but I was not aware of any way to achieve this in a web browser. I did a bit of Googling, and soon found the new “prefers-color-scheme” CSS media query which was precisely what I needed. Unfortunately, the prefers-color-scheme media query was supported only in the very latest Safari for Mac OS, and an upcoming version of Chrome. Regardless, I applied the necessary CSS overrides to create support for Dark Mode using prefers-color-scheme, and now Scorecard.dev switches its color scheme based on the user’s preference.

Scorecard.dev with and without dark mode

How to get in contact with me

My email address is neil@neilonsoftware.com. You can read more about my background on my blog neilonsoftware.com, as well as on my LinkedIn profile. My full resume is available at neilgreen.dev/resume