Skip to content

For Developers

Alfred J Lin edited this page Jun 2, 2026 · 1 revision

This section is for developers building or extending the theme. While the rest of the wiki documents components for content editors, these pages explain how the design system works under the hood and how to build a new component from scratch so it matches the rest of the system automatically.

If you only configure components in the Layout Builder, you don't need this section. If you write Twig, CSS, or .component.yml files, start here.

The big picture: a two-layer token system

Everything in the theme is built on a strict separation of concerns. There are three layers, and styling flows in one direction only:

  1. Layer 1 — Primitives. Raw values defined once as CSS custom properties in css/base.css (:root): the Maryland color palette (--maryland-red, the gray ramp) and the spacing scale (--space-xs--space-3xl). The .dark-theme class re-maps these same token names to inverted values.
  2. Layer 2 — Semantic utility classes. The "design system surface" that you actually write in markup. Four clean namespaces in base.css: c-* (color roles), t-* (typography ramp), s-* (spacing steps), and ani-*/i-* (animation and icons). Each one references a Layer 1 token via var() — never a hard-coded value.
  3. Layer 3 — Components (SDC). Drupal Single Directory Components under components/. Their *.twig files apply the Layer 2 utility classes to express design intent, and their *.css files contain only structural layout (flex, grid, positioning, aspect-ratio) — using var(--token) primitives on the rare occasion they must set a color or spacing value directly.
:root primitives  →  c-/t-/s-/ani- utilities  →  components apply utilities in Twig
   (--maryland-red,        (the design-system           (structural layout only
    --space-md, …)          surface you write)            stays in component CSS)

The golden rule

Express the role, not the raw value.

When you style a component, you never pick a hex color, a pixel measurement, or a font size. You pick a color role (c-content-secondary), a spacing step (s-stack-small), and a type ramp slot (t-body-small). The system decides the actual value — and that is exactly what makes three things work for free:

  • Dark mode — flipping .dark-theme on an ancestor recolors the whole subtree, because every utility resolves through var(--token).
  • Brand/theme changes — update a token in base.css once, and every component follows.
  • Responsiveness — the type ramp and spacing utilities already step up at the breakpoints (mainly 768px), so you don't write media queries for them.

No build step

The theme has no build tooling — no Sass, no webpack, no npm scripts. css/base.css is the hand-maintained, precompiled stylesheet, and JavaScript libraries ship pre-minified. Drupal's own library aggregation (umdlib_umdds.libraries.yml) loads everything. You edit CSS and Twig directly and clear the Drupal cache to see changes.

Where to go next

  • Design Tokens and Utility Classes — the full reference for the c-*, t-*, s-*, and ani-* classes you write in markup, plus the ready-made .button.umd-lib base class.
  • Building a Component — the fast recipe: SDC anatomy, the spacing/color/type rules, the non-negotiable house rules, a worked example, and a 60-second checklist for shipping a component that matches the system.
  • Design Tokens, Color, Spacing — the Foundations pages documenting the underlying Layer 1 token values.

The canonical source these pages are derived from is DESIGN-SYSTEM.md in the theme repository, generated by reading css/base.css end-to-end and auditing every component.

Clone this wiki locally