Foundations
Theming
Light and dark themes, the theme switch, and overriding a component on one element.
Every colour token has a light and a dark value. The CSS system switches on the root: by default it follows prefers-color-scheme, and data-theme="light" or data-theme="dark" on <html> forces one. .theme-light and .theme-dark pin a subtree, which is how the live examples on these pages show both themes side by side.
Muted text on the light card.
Muted text on the dark card.
The theme switch
/site/site.js applies the stored preference (System, Light or Dark) before first paint and exposes window.ChiTransitSite:
ChiTransitSite.preference() // "system" | "light" | "dark", persisted per browser
ChiTransitSite.effective() // "light" | "dark"
ChiTransitSite.setPreference(p) // change and persist
ChiTransitSite.onChange(fn) // fn({ preference, effective }) after every change
Load it synchronously in <head>. Any .site-theme [data-theme-value] button is kept in sync with aria-pressed; the switch in this site's header is a segmented control wired that way.
Overriding a component
Each component reads its token behind an override: var(--button-radius, var(--ct-button-radius)). Change the token in tokens/components.tokens.json to restyle every instance, or set the property without the ct- prefix on one element or subtree:
.checkout .ct-button { --button-radius: var(--radius-pill); }
The tokens and base styles sit in cascade layers (ct.tokens, ct.base), so any unlayered page rule wins over them without !important.
iOS
Colours ship as DesignTokens values with light and dark variants; the app reads them through asset-catalog colour sets with Any / Dark appearances, named after the tokens. See SwiftUI mapping.