When building and designing for the web we want to be productive and efficient. It's often easy to forget what's needed to achieve that, especially when we're lost in complex systems. Productivity requires predictability, ease of use, and intuitiveness. Efficient means little cognitive overhead and making prototyping as rapid as possible. In essence, this means an API is guessable.
Let's consider a UI library that has a collection of colors. We have a Text
component that accepts a color
prop for any color in the design system.
We can specify the color naming in a multitude of ways. One of the more popular
naming schemes is reminiscent of color names, so you have something like:
export const colors = {
black: '#000',
darkGray: '#333',
gray: '#666',
lightGray: '#999',
lighterGray: '#ccc',
white: '#fff'
}
When you have a component that needs gray text, that's straightforward:
<Text color="gray">My gray text</Text>
Though, this gets problematic when you need colors that are slightly lighter
or darker. If you're using gray
and need something different you're left
wondering: Is the next color lightGray
, mediumGray
, or is it something
different?
When you apply specific naming to an idea or concept you can only override it with more specificity. This breaks down as colors palettes evolve and grow.
If you need to introduce a color between lightGray
and lighterGray
what do
you do?
This is important to consider for design systems, especially for large
organizations, because these decisions have to stand the test of time.
They also have to be practical for designing and developing. If you're
stuck trying to remember the color scale for purple
you're going to be
dealing with unnecessary cognitive overhead.
Libraries like Tachyons, Basscss, and Styled System helped to introduce the concept of numeric scales at the design system level. This helped to remove the overhead of figuring out color or spacing because you could increment classes or props to achieve a larger/smaller or darker/lighter effect. This helped to pioneer rapid UI development and is why I use Styled System to this day.
Consider the following component that could be found anywhere in an app or site:
<Box padding={3} backgroundColor="tomato" />
Now consider the scenario where a stakeholder wants more padding and the background to be gray. As a designer or engineer you can quickly start to enumerate through possible scales.
<Box padding={4} backgroundColor="gray" />
They're happy with the change, but want the gray to be darker. You know that
gray
has a scale so you can target that near the middle:
<Box padding={4} backgroundColor="grays.5" />
You're working quickly and your stakeholder can't believe how quickly you're making these changes. But, they want the gray to be a bit lighter. No problem, you adjust the scale:
<Box padding={4} backgroundColor="grays.6" />
Oops, upon live reload you realize you went the wrong way, so you go the other direction:
<Box padding={4} backgroundColor="grays.4" />
Now you've achieved the desired results and we can unpack what just happened.
Numeric scales are crucial for a few reasons. They allow engineers to quickly move up and down scales to find the values that stakeholders are hoping for. They also allow designers to work with constraints that fit 90% of use cases so consistency is maintained for most scenarios. Magic numbers and values are kept to a minimum.
For everyone building with the system, it's guessable. Quickly being able to move up and down a scale, even when you can't remember which way is which, allows you to rapidly make tweaks that adhere to a larger design system.
These scales can persist throughout a design system wherever relevant.
When this occurs, many aspects suddenly become guessable without
familiarity with the property. If you're dealing with box shadows and
you need a Card component to have more depth and you're presented with
<Card shadow={2} />
you will have an idea on how to increase or
decrease that depth by leveraging the scale.
GraphQL is a technology that has gained a lot of popularity for a multitude of reasons. Firstly, it's great for clients to be able to query for the exact data that they want in one go, but I think it's discoverability has really helped it take off.
With GraphQL you can generate documentation so users of an API don't have to guess, or when they do, they can make educated guesses based on the documentation when trying to figure out a query.
Then comes GraphiQL, it improves discoverability immensely with the ability to
try queries and even use keyboard shortcuts, like CMD + SPACE
, to see what
possible properties are available. It's a dream and improves the developer
workflow immensely.
Something that's guessable needs to be memorable. This is often achieved by an API that is explicit, simple, and draws from concepts that are already understood by end users.
Gatsby Themes introduced a concept known as component shadowing and I think it's a perfect example. The idea with this feature is that users of a theme can override a component or functionality by using a folder naming convention.
For example, if you've installed gatsby-theme-blog
and you want to
customize the author bio you can introduce your own component following
a particular naming convention: src/gatsby-theme-blog/components/author-bio.js
.
Now, in place of the theme's AuthorBio component, the user's component
is now rendered.
This convention is memorable because the contract is rather thin and
users are used to interacting with the file system, especially when
creating components. The steps to override a theme's component becomes
touch src/THEME-NAME/components/COMPONENT-NAME.js
.
After my first time shadowing a component I never needed to consult the docs again. I accidentally absorbed and remembered the API.
An API that's meant to be guessable should also be easy to try. This means that when you're not sure whether you should increment or decrement a number you're empowered to give it a go with your best guess. Empowerment to give it a try means that the tooling ensures that this is efficient. That includes live reloading, source maps, and projects like React Devtools that improve the debugging experience.
This inspires confidence in the system because you're not punished for guessing.
When a designer or developer knows a change will show up in the browser in one second, they're happier to work with the constraints because the feedback loop is instant. They can iterate towards the end product without having to consult long docs or reading the source code of a component. The process is fluid.
A guessable API does what you think it should whenever you employ it. This is
something I think yarn
gets right on a few levels. By default yarn runs the
install. This means the default entrypoint of the CLI does what you'd typically
want with your package manager. It also automatically adds a dependency to your
package.json
after you yarn add
. This is another subtle difference but greatly
benefits new users.
It even removes the need for guessing because default behaviors are built in.
I wouldn't call yarn
perfect either. Some of it's guessability is lost when you
consider it's intended to be a replacement for npm
. Commands are subtly different,
making switching between the tools cumbersome and frustrating, and there aren't built
in aliases for the same commands.
npm
standardized on unix-y conventions for commands, so existing programs (like ls
)
have a similar counterpart in npm
. However, in yarn
, you're forced to write out list
.
This makes the tool less guessable, and for no real benefit.
It makes sense to have different needs in an API. In fact you might even want to
purposefully evolve the API. However, from a tooling standpoint like yarn
,
it'd be nearly no overhead to alias the ls
command with a soft warning to use
list
.
When making guessable software we often overlook this aspect. We should more
gently steer towards the direction we might think is better, but to respect
the tooling that came before it. We should ease the transition and not leave
people getting frustrated and then typing in -h
and being presented with a
terminal filled with 50 or so options.
When two tools diverge, or address the same tooling concerns like npm and yarn we end up with a lot of cognitive overhead. This is a massive impedance to guessability. Five years ago you could bootstrap nearly every Node.js project with
npm install && npm start
Now, as a frequent user of open source projects you have to cat package.json
right
after you clone a project to check for script names, workspaces, and other features
that will dictate which CLI tool to use.
I'm not complaining, because I don't contribute to either project, and have noticed that they've both made eachother better. But this type of overhead when you can work in more than 20 projects in a single week can be daunting. It's definitely not guessable.
As tools and ideas begin to converge we might even be lucky enough to see
common standards become accepted. This includes ideas like the Styled System
Theme Specification
that seeks to standardize the commonalities in a design system's theme.js
.
When a community arrives at a common spec, everyone benefits.
I hope to see other tools converge on standards and guessable APIs that leave end users feeling productive and confident.
Buy in is often represented by tooling authors and the greater community. When patterns develop we start to see conventions. When we're lucky we also see standards.