An example component hierarchy

Understanding Component Hierarchy

Overview

Structure

Understanding the Hierarchy

The main purpose of the hierarchy is to provide a focused scope of responsibility for individual components and meaningful relationships between them. This allows components to be composed in ways that provide a healthy balance of structure and flexibility. Without structure the components become misaligned and inconsistent. But without flexibility, the components become brittle and tightly constrained in their use. For a healthy component library, you need both.

If we think of flexibility and structure as a spectrum, different types of components will live along that scale. Primitives at the bottom of the hierarchy are the most flexible. And as you move up in the hierarchy to Elements and Compositions, components become less flexible and more structured. This allows more specific, opinionated components within your library to be consistent, while also providing space for lower-level components to maintain their flexibility.

But these components do not exist in isolation. The hierarchy also establishes meaningful relationships between them. Elements are used to build Elements, which are composed to create Compositions. For example, this LeftNav is a Composition of a Box Primitive and Nav, List, ListItem, Link Elements.

Note: This Composition would likely take a prop like items and map over them to create each ListItem, but to keep the example straight-forward, I’ve omitted that.

These relationships allow LeftNav to be an opinionated instance of its structure. However, if we needed to create another instance that allowed Icons to be paired with the Links, we could compose the Primitives and Elements instead of modifying this instance to support it. We’re providing flexibility and structure through composability.

Primitives

Primitives are our lowest-level component abstraction. They are only constrained by their purpose and our tokens and theme values. Because they are so flexible and can be formed into many different Elements, there are relatively few of them. They are solely responsible for visual concerns. They don’t think about different states or behaviors.

In the internal library you’ll want to use Primitives often to create other Elements and Compositions, but external consumers likely won’t use them directly as often as other components. To understand why, let’s look at an example, Stack. Stack is an opinionated layout Composition. It exists to help create vertical rhythm (spacing) between elements.

By using Stack, its child element Stack.Item, and the space prop, we can quickly create even spacing between each element. But if that doesn’t work for our use case, we could use the lower-level Element, Flex, that it’s built upon. Flex is a layout component that knows about our spacing scale and how to use CSS Flexbox for aligning items. Let’s say instead of even spacing between all elements, we need a little more space around the middle child.

Great! Not too much more work. And if for some reason we needed even more control, we could use our lowest-level Primitive, Box.

Note: This high degree of customization also comes with a responsibility to use them in ways that are harmonious with the rest of the library. You still have the safeguards of using system values, but you should know how to keep UI coherent.

Elements

Like their chemical counterparts, Elements cannot be broken down into a simpler substance without losing their essential characteristics. They are closely aligned to HTML elements but are not limited to them. While they are still highly flexible, they are more constrained to a particular instance. Some will have particular variants and states, but they typically will not have behaviors. They can be used in a variety of contexts and are only concerned with their own internal attributes. In the LeftNav example above, these are the Nav, List, ListItem, Icon, and Link Elements. You can also extend Elements to create more specific instances. For example, we could have a NavLink that activates a variant when it's the current route:

Compositions

Compositions are our highest-level component abstraction. They can be composed of Primitives, Elements, or, in some cases, other Compositions. While they have distinct parts, they also have associated behaviors. Some example behaviors are: how and where a popup appears and disappears, how a dropdown menu responds to keyboard commands, and how a side panel opens and closes. These behaviors are often connected to states (open, closed, hover, focus, etc) and accessibility attributes. Ideally, all of these behaviors, states, and accessibility attributes are composable as well. This allows us to build dropdown menus to exhibit the some of the same behaviors as our tooltips even though the UI is completely different. You could build entirely new compositions with behaviors from several existing compositions and have them feel cohesive. Let’s add a little to our LeftNav example from earlier.

Wrapping Up

The component hierarchy focuses the component’s scope of responsibility, creates meaningful relationships between components, encourages composability, and provides balance between flexibility and structure in our component library. This empowers external teams to build and evolve their UI more effectively and allows maintainers to more sustainably support them.

FAQ

“How are Primitives, Elements, and Compositions different from Atomic Design’s Atoms, Molecules, and Organisms?

To me, Primitives are a lower level than AD’s Atoms, though I understand that’s subjective. Elements are effectively Atoms and Molecules are Compositions. (Tokens would be subatomic particles, but we can talk about that another time.) I also think anything large enough to be considered an “Organism” is too large to live in a component library. It makes more sense for that to live in a product application.

The distinction between Primitives and Elements is that Elements are one type of thing (Button, Icon, Paragraph, etc), and Primitives have the flexibility to be lots of types of things. If we want to use a cellular analogy: Elements are a single type of cell (skin, brain, blood) and Primitives are closer to stem cells.

design systems @workday