Component Library 2.13.1 (configured with Dummy App)

Previews

      • Button
      • Hyper Link
      • Icon
        • Combobox
        • Email
        • Radio
        • Select
        • Telephone
        • Text
        • Textarea
      • Navigation Banner
      • Paginator
      • Article
        • Default
        • Hero Only
        • Multi Panel
      • Article Listing
      • Carousel
      • Fault Tolerance
        • Default
        • Mobile Accordion
      • Header
        • Alignment
        • Background Hero With Breadcrumbs
        • Default
        • Alignment
        • Default
      • Article Summary Card
        • Default
        • Error
        • Filled
        • No Legend
        • Broken Embed
        • Default
      • Image
      • List
        • Default
        • Quote On Surface
        • With Iframe Media
        • With Media Fit
        • Default
        • Quote On Surface
  • Style Guide

No matching results.

Pages

  • Developer Guide
  • Overview
  • Colours
  • Typography
  • Spacing
  • New Components
  • Interactive Behaviour
  • Component Variants
  • CSS Utilities
  • Extending Components
  • CSS Boundaries

No matching results.

CSS Utilities

CSS Utilities

SCSS mixins and conventions beyond spacing and layout (covered in the Spacing doc).

If a CSS pattern repeats across components, extract it as a mixin in _mixins.scss. New mixins are encouraged.

Import Convention

Every SCSS partial must begin with:

@use "../utilities/" as *;

Mixins

Mixin Output
@include card border-radius: 8px; border: 2px solid var(--border-mono-medium); padding: 24px
@include ulreset list-style: none; padding: 0; margin: 0
@include hidescrollbar Hides scrollbar cross-browser, preserves scroll
@include hidden display: none !important — hard hide, not accessible-hidden
@include light-surface($bg, $raised: null) Paints $bg and publishes the light surface contract (see Colours, Surfaces)
@include dark-surface($bg, $raised: null) Paints $bg and publishes the dark surface contract
@include inherit-surface($bg: transparent) Paints $bg and inherits the enclosing surface's contract

Cascade Layers

application.scss declares the layer order once, the component hierarchy read inside out, and loads every partial into one with meta.load-css:

@layer vendor, base, components, patterns, panels, pages, utilities;

Vendor CSS (Swiper, Trix) sits under ours; base element styles under components; patterns over components; panels over patterns; pages over panels; the utility classes (.visually-hidden, the iOS scroll lock) on top. Everything the engine ships is layered, so a host app's own stylesheet, which is unlayered, beats every engine rule by definition rather than by being imported last. The utilities/ index forwards mixins and functions only; the utilities' CSS is loaded into its layer by the entry file, so a partial's @use "../utilities/" as * emits nothing.

Two consequences. A rule in a higher layer wins regardless of specificity, so a pattern rule that catches a component (.text-card > a matching a.button) now beats the component's own rule; scope such rules to what they mean (> a:not(.button)). And !important reverses the order: an important declaration in a lower layer beats one in a higher layer, which is what visually-hidden and hidden want and nothing else does.

!important

Kept only where it is a decision, each with its reason: the visually-hidden and hidden mixins, the iOS overflow lock, the .p-* padding utilities, the combobox [hidden] enforcement, and vendor CSS. Nothing else in the tree. Each is marked with a stylelint-disable comment giving the reason. A new !important needs the same and a mention in the PR; stylelint fails it otherwise.

Declared, Not Inferred

A component that renders something declares it as a data attribute set from Ruby; the stylesheet reads the attribute and never sniffs the markup. ButtonComponent emits data-icon when it renders an icon and _buttons.scss styles .button[data-icon]; there is no :has(.material-symbols-outlined) anywhere. The same shape as the panel header's align: / variant:, the text card's leading: and the image's fit:.

Raised Surfaces and Card Shadow

A card that sits on a surface fills from --surface-raised when the surface publishes one and publishes a light surface inside, so its text reads dark on the pale fill whatever the panel around it is. raised-surface($fallback) encodes that rule; cards call it, panels never do:

.my-card { @include raised-surface(var(--bg-white)); }

card-shadow is the card elevation. It bundles a backdrop blur, which creates a stacking context, so for a hover or a transition pass card-shadow($blur: false).

Button Variants

The button skin belongs to the .button class; a native <button> without it is a plain control (no background, border, padding or margin, inherits its font and colour) from styles/_forms.scss, so a hand-written control button needs no reset. A layout the button can take is a variant the button owns in _buttons.scss; style is its colour. A container asks for a variant, it never restyles the button: ButtonComponent.new(button:, variant: :bar) emits data-variant="bar", and for a button a host renders into a container's slot, the container names the variant with one line, .header__better-action-links .button { @include button-action; }. The variants are mixins in components/_button-variants.scss, a module that emits no CSS of its own, so a panel can @use it without pulling the button's stylesheet into its layer.

variant what it is who asks for it
:bar a tab in a control-height bar: no radius or border, full height, xs type, inset-underline hover the header's sector buttons
:stretch fills its column at the large control height the footer's cookie-settings button
:action a text-and-icon action on the dark header bar whatever a host renders into the header's action slot
:menu a full-width, left-aligned row in a menu list sector links a host renders into the mobile nav

.button-content is selected only in _buttons.scss. The navigation item's trigger is its own element, .navigation__menu-item__button containing .navigation__menu-item__link, not a ButtonComponent, and _menu-item.scss owns it.

Nesting Depth

Maximum 4 levels. Prefer 2–3.

Known Inconsistencies

The following exist in the codebase but are not the correct pattern:

  • _buttons.scss has hard-coded rgba() values for disabled states — use CSS custom properties from _colours.scss
Component Structure & Variants Extending Components