Authoring components
How to write and structure the CSS of a design-system component.
Recovered from the predecessor. This was
development/component-structure.mdxinreference-guide— added 2021-12-06, never changed, and with no equivalent anywhere on the live docs site. It is the one page that governs all the others. Spot-checked 2026-09-04 againstsource-css/at pin98f2eb1349:button.cssstill obeys every rule below, so the contract is live and merely undocumented.
Three principles
- Structured naming — every component uses a prefix and follows BEM.
- Overwritable classes — every class must be overridable by a Tailwind utility.
- Inherited attributes — every attribute is set by inheriting Tailwind classes, via
@apply, rather than as a raw property.
Prefix
App components start with c-. Components for the marketing pages start with c_h-.
/* App */
.c-button {}
/* Marketing pages */
.c_h-button {}
That split is how one repo once held both the app and the marketing site. Marketing is out of scope for v2 — an agency owns brand — so new work should only ever mint c-. The prefix is recorded because it explains the estate, and because it is the join key that lets one page serve both Rails and React.
BEM, without grandchildren
.c-btn {} /* Block */
.c-btn__icon {} /* Element */
.c-btn--green {} /* Modifier */
.c-btn__icon--green {} /* Element + modifier */
Avoid selectors two or more levels deep:
/* Don't */
.c-nav__list {}
.c-nav__list__item {}
/* Do */
.c-nav__list {}
.c-nav__item {}
Specificity stays at zero
This is the load-bearing rule and the reason for @apply. Component classes keep specificity low enough that a Tailwind utility always wins, so a consumer can adjust one instance without fighting the system or writing an override.
Set every attribute through @apply, not as a raw declaration:
@layer components {
.c-button {
@apply px-2.5 py-1.5 cursor-pointer bg-azure-500 text-white h-8 inline-flex items-center;
}
}
Declaring properties directly raises specificity and breaks the contract. The one routine exception in the shipped CSS is transition, which has no utility that expresses it.