Authoring components

How to write and structure the CSS of a design-system component.

Recovered from the predecessor. This was development/component-structure.mdx in reference-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 against source-css/ at pin 98f2eb1349: button.css still obeys every rule below, so the contract is live and merely undocumented.

Three principles

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.