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.
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.
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.
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.
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.
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:
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.
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:
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.
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.
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.
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.
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.
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.
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:
Specifically for Scorecard.dev, here is how I created each of the circles-with-lines effects:
There were two situations where I wanted the bars on a scorecard to be animated:
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.
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:
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.
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.
My email address is firstname.lastname@example.org. 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