Lessons learned from building a blog component system

A great component system allows teams to build new components faster, more consistently, and with less iteration. While building mine for this blog, I encountered a few things that made me backtrack and redo a few things. Here are some lessons learned.

Mind your margins

Margin collapse is the behavior for top and bottom margins to merge in the default block context. As Josh W Comeau illustrates, the rules are quite complex.

In a flex or grid flow context, margin collapse is not enabled. This causes issues for components like headings and paragraphs, which depend on the margin collapse behavior for styling. These same components will look odd in flex and grid containers.

As developers continue to work with more flex and grid, margin collapse becomes an obsolete and confusing standard to work with. Removing all margin-tops from components, especially typographic elements, makes content layout much easier to work with.

Full bleed takes blood, sweat, and tears

A full bleed layout is especially useful in app-like instances or for design flourishes like full-width images. Most of the time, for a content-based website, we do want to enforce a set width.

A full bleed escape hatch can be done in 3 ways:

Margin overflow -50v

.full-width {
  width: 100vw;
  position: relative;
  left: 50%;
  right: 50%;
  margin-left: -50vw;
  margin-right: -50vw;
}

This method is the most common approach because it acts as a hack on top of an existing width-constraint container. It is also easy to add, as it allows you to enable full bleed at any level. However, it is not foolproof. Some elements may overflow the page, causing the page to scroll. This can be disabled with an overflow-x: hidden on the body, but this prevents position: sticky elements from working.

:not(.full-bleed) selector

*:not(.full-bleed) {
  position: relative;
  max-width: 40rem;
  margin: auto;
}

This “unselector” seems useful at first glance until you realize that only immediate child components can go full bleed. Nested components are not able to break out of their parents' constraints. This method also fails to work with max-width units such as em and ch, as immediate child components can affect their max-width, resulting in a layout with inconsistent widths.

Grid layout

.wrapper {
  display: grid;
  grid-template-columns:
    1fr
    min(65ch, 100%)
    1fr;
}
.wrapper > * {
  grid-column: 2;
}
.full-bleed {
  width: 100%;
  grid-column: 1 / -1;
}

The third solution uses the CSS grid layout, which is widely supported by modern browsers. The only downside is that to break out of the full-bleed, all the parents of the element must have both the wrapper and full-bleed class names. Breaking out will become easier when display: contents is more widely adopted in browsers, which allow child elements to participate in the grid or flex-flow contexts of their parents. You can read more able this new CSS property here: https://css-tricks.com/get-ready-for-display-contents/.

Forms, forms, and forms

Forms are the most critical part of a component system because form interactions are so complex. Before building one, consult all the possible uses of a form and design an API that would be able to fit within those constraints.

Badly designed form components can result in reworks because of one edge case. For example, in an attempt to simplify the form API, I used the label text for the name attribute of the radio input component. This worked until I encountered a situation until the same label was needed but in different forms.

Other instances of edge cases that may result in redesigns:

  • Error states and error messages
  • Disabled state and reasons for being disabled
  • Async input forms and loading states
  • Accessibility

In general, avoid building your form API. It is better to learn from existing libraries like https://react-spectrum.adobe.com/ or https://material-ui.com/ where form components have been battle-tested.

Conclusion

These lessons were hard-earned from building the blog component system. If I had known these before starting, I definitely would have saved myself a huge chunk of time. Hopefully, you can apply these lessons to your component system and save some time.