Skip to content

Tokens

Nothing on this page is typed by hand. Every name, every value and every contrast figure is read out of packages/tokens/dist/tokens.json when the site builds, and the ratios are measured by the same module the token test suite uses. A token added to the source appears here. A value changed in the source changes here. A pairing that stops meeting its declared minimum fails this build before it can be published.

Tokens
217
Colors
142
Declared contrast pairings
74
Held to AAA
52

13 semantic color tokens carry no contrast declaration: light.surface.overlay, light.brand.active, light.accent.subtle, light.border.base, light.mockup.device, light.mockup.bezel, dark.surface.overlay, dark.brand.active, dark.brand.subtle, dark.accent.subtle, dark.border.base, dark.mockup.device, dark.mockup.bezel. These are surfaces and fills that never sit behind text on their own. Anything that does needs a declaration, because a pairing with no declaration is a pairing the gate cannot see.

How the layers work

primitive    color.green.400        #0c5449         the ramp
semantic     semantic.light.brand   {color.green.400}   what it is for
component    --wsu-brand                            what components may use

Components reference the semantic layer only. Reaching past it to a ramp value is how a system stops being themeable: the value is right in light, wrong in dark, and invisible to the contrast gate because no semantic token declares it. The CSS build fails on var(--wsu-green-400) inside a component or the chrome.

The ramps match waynestate/base-site stop for stop, so green-400 means the same thing to a C&IT developer as it does to a marketing developer. Extension stops are allowed only on numbers base-site does not define, and each has to justify itself in the token source.

Contrast

Every color pairing the system relies on is declared on the tokens themselves, with the minimum it has to meet and, often, why. The test suite reads those declarations back, so adding a color without one is caught by a coverage test rather than discovered later.

The minima are not all the same. Body text is held to 7:1, which is WCAG AAA rather than the 4.5:1 AA floor, because the WSU palette reaches it. Helper text and captions are held to 4.5:1. Borders and focus rings are held to 3:1, which is the non-text requirement in 1.4.11.

Light

37 declared pairings in the light theme, sorted by how close each one sits to its own minimum
PairingSampleMeasuredDeclared minimumResult
text.subtleonsurface.baseAA. Captions and helper text only, never primary content#71717a on #FFFFFF4.83:14.5:1 AA 1.4.3 Meets 0.33 to spare
status.infoonsurface.base#0E5A7D on #FFFFFF7.56:17:1 AAA 1.4.6 Meets 0.56 to spare
status.info-ononstatus.info#FFFFFF on #0E5A7D7.56:17:1 AAA 1.4.6 Meets 0.56 to spare
status.dangeronsurface.base#9B1C2E on #FFFFFF8.07:17:1 AAA 1.4.6 Meets 1.07 to spare
status.danger-ononstatus.danger#FFFFFF on #9B1C2E8.07:17:1 AAA 1.4.6 Meets 1.07 to spare
status.warningonsurface.base#634D00 on #FFFFFF8.10:17:1 AAA 1.4.6 Meets 1.10 to spare
status.warning-ononstatus.warning#FFFFFF on #634D008.10:17:1 AAA 1.4.6 Meets 1.10 to spare
brand.baseonbrand.subtle#0c5449 on #F2F7F68.15:17:1 AAA 1.4.6 Meets 1.15 to spare

Dark

37 declared pairings in the dark theme, sorted by how close each one sits to its own minimum
PairingSampleMeasuredDeclared minimumResult
chrome.bar-mutedonchrome.bar#F2F7F6 on #0c54498.15:17:1 AAA 1.4.6 Meets 1.15 to spare
text.subtleonsurface.base#a1a1aa on #1425216.22:14.5:1 AA 1.4.3 Meets 1.72 to spare
status.infoonsurface.base#8CC8E8 on #1425218.76:17:1 AAA 1.4.6 Meets 1.76 to spare
chrome.bar-textonchrome.bar#FFFFFF on #0c54498.82:17:1 AAA 1.4.6 Meets 1.82 to spare
border.strongonsurface.raisedinputs sit on cards and popovers too, not only on the page#a1a1aa on #1F322E5.27:13:1 1.4.11 non-text Meets 2.27 to spare
status.nonproduction-ononstatus.nonproduction#0A1916 on #F5A3B89.29:17:1 AAA 1.4.6 Meets 2.29 to spare
status.dangeronsurface.base#FFB4AB on #1425219.39:17:1 AAA 1.4.6 Meets 2.39 to spare
status.info-ononstatus.info#0A1916 on #8CC8E89.91:17:1 AAA 1.4.6 Meets 2.91 to spare

The gold

PMS 1225c sits at 1.5:1 against white, so it is never a text color on a light surface and never the foreground of a control. The foreground on gold is always ink. That is declared on the token, which is why a component cannot quietly get it wrong.

Color

The primitive layer. These match waynestate/base-site stop for stop, so green-400 means the same thing to a C&IT developer as it does to a marketing developer. Components must never reference a ramp value directly. The CSS build fails if one does.

Color ramps: 50 tokens, generated from packages/tokens/dist/tokens.json
CSS custom propertyValueToken
--wsu-green-25#F2F7F6color.green.25
--wsu-green-50#cedddbcolor.green.50
--wsu-green-100#9ebbb6color.green.100
--wsu-green-200#6d9892color.green.200
--wsu-green-300#3d766dcolor.green.300
--wsu-green-400#0c5449color.green.400
--wsu-green-500#0b4a40color.green.500
--wsu-green-600#094038color.green.600
--wsu-green-700#08352fcolor.green.700
--wsu-green-800#062b27color.green.800
--wsu-green-900#05211ecolor.green.900
--wsu-gold-50#fff5d6color.gold.50
--wsu-gold-100#ffebadcolor.gold.100
--wsu-gold-200#ffe085color.gold.200
--wsu-gold-300#ffd65ccolor.gold.300
--wsu-gold-400#ffcc33color.gold.400
--wsu-gold-500#edbd2ccolor.gold.500
--wsu-gold-600#dbae25color.gold.600
--wsu-gold-700#c89f1fcolor.gold.700
--wsu-gold-800#b69018color.gold.800
--wsu-gold-900#a48111color.gold.900
--wsu-gold-950#634D00color.gold.950
--wsu-neutral-50#fafafacolor.neutral.50
--wsu-neutral-100#f4f4f5color.neutral.100
--wsu-neutral-200#e4e4e7color.neutral.200
--wsu-neutral-300#d4d4d8color.neutral.300
--wsu-neutral-400#a1a1aacolor.neutral.400
--wsu-neutral-500#71717acolor.neutral.500
--wsu-neutral-600#52525bcolor.neutral.600
--wsu-neutral-700#3f3f46color.neutral.700
--wsu-neutral-800#27272acolor.neutral.800
--wsu-neutral-900#18181bcolor.neutral.900
--wsu-neutral-950#09090bcolor.neutral.950
--wsu-ink-700#314842color.ink.700
--wsu-ink-800#1F322Ecolor.ink.800
--wsu-ink-900#142521color.ink.900
--wsu-ink-950#0A1916color.ink.950
--wsu-ink-975#050D0Bcolor.ink.975
--wsu-white#FFFFFFcolor.white
--wsu-black#09090bcolor.black
--wsu-functional-danger-dark#9B1C2Ecolor.functional.danger.dark
--wsu-functional-danger-light#FFB4ABcolor.functional.danger.light
--wsu-functional-success-dark#0F5132color.functional.success.dark
--wsu-functional-success-light#8FE0AEcolor.functional.success.light
--wsu-functional-warning-dark#634D00color.functional.warning.dark
--wsu-functional-warning-light#FFD65Ccolor.functional.warning.light
--wsu-functional-info-dark#0E5A7Dcolor.functional.info.dark
--wsu-functional-info-light#8CC8E8color.functional.info.light
--wsu-functional-nonproduction-dark#8B2145color.functional.nonproduction.dark
--wsu-functional-nonproduction-light#F5A3B8color.functional.nonproduction.light

The layer components are allowed to use. Every name says what the color is for rather than what it looks like, which is what makes one component set work in two themes.

Semantic color, light: 46 tokens, generated from packages/tokens/dist/tokens.json
CSS custom propertyValueToken
--wsu-surface#FFFFFFsemantic.light.surface.basepoints at color.white
--wsu-surface-sunken#fafafasemantic.light.surface.sunkenpoints at color.neutral.50
--wsu-surface-raised#FFFFFFsemantic.light.surface.raisedpoints at color.white
--wsu-surface-overlay#FFFFFFsemantic.light.surface.overlaypoints at color.white
--wsu-surface-inverse#18181bsemantic.light.surface.inversepoints at color.neutral.900
--wsu-text#18181bsemantic.light.text.basepoints at color.neutral.900
--wsu-text-muted#3f3f46semantic.light.text.mutedpoints at color.neutral.700
--wsu-text-subtle#71717asemantic.light.text.subtlepoints at color.neutral.500
--wsu-text-on-inverse#f4f4f5semantic.light.text.on-inversepoints at color.neutral.100
--wsu-brand#0c5449semantic.light.brand.basepoints at color.green.400
--wsu-brand-hover#0b4a40semantic.light.brand.hoverpoints at color.green.500
--wsu-brand-active#094038semantic.light.brand.activepoints at color.green.600
--wsu-brand-subtle#F2F7F6semantic.light.brand.subtlepoints at color.green.25
--wsu-on-brand#FFFFFFsemantic.light.brand.onpoints at color.white
--wsu-accent#ffcc33semantic.light.accent.basepoints at color.gold.400
--wsu-accent-subtle#ffebadsemantic.light.accent.subtlepoints at color.gold.100
--wsu-on-accent#09090bsemantic.light.accent.onpoints at color.neutral.950
--wsu-border#d4d4d8semantic.light.border.basepoints at color.neutral.300
--wsu-border-strong#71717asemantic.light.border.strongpoints at color.neutral.500
--wsu-border-brand#0c5449semantic.light.border.brandpoints at color.green.400
--wsu-focus-ring#0c5449semantic.light.focus.ringpoints at color.green.400
--wsu-focus-offset#FFFFFFsemantic.light.focus.offsetpoints at color.white
--wsu-chrome-masthead#094038semantic.light.chrome.mastheadpoints at color.green.600
--wsu-chrome-masthead-text#FFFFFFsemantic.light.chrome.masthead-textpoints at color.white
--wsu-chrome-masthead-muted#F2F7F6semantic.light.chrome.masthead-mutedpoints at color.green.25
--wsu-chrome-bar#0c5449semantic.light.chrome.barpoints at color.green.400
--wsu-chrome-bar-hover#0b4a40semantic.light.chrome.bar-hoverpoints at color.green.500
--wsu-chrome-bar-text#FFFFFFsemantic.light.chrome.bar-textpoints at color.white
--wsu-chrome-bar-muted#F2F7F6semantic.light.chrome.bar-mutedpoints at color.green.25
--wsu-chrome-on-chrome-focus#ffd65csemantic.light.chrome.on-chrome-focuspoints at color.gold.300
--wsu-chrome-mark#0c5449semantic.light.chrome.markpoints at color.green.400
--wsu-mockup-device#09090bsemantic.light.mockup.devicepoints at color.black
--wsu-mockup-bezel#71717asemantic.light.mockup.bezelpoints at color.neutral.500
--wsu-mockup-panel#050D0Bsemantic.light.mockup.panelpoints at color.ink.975
--wsu-mockup-screen#18181bsemantic.light.mockup.screenpoints at color.neutral.900
--wsu-on-mockup#f4f4f5semantic.light.mockup.onpoints at color.neutral.100
--wsu-danger#9B1C2Esemantic.light.status.dangerpoints at color.functional.danger.dark
--wsu-on-danger#FFFFFFsemantic.light.status.danger-onpoints at color.white
--wsu-success#0F5132semantic.light.status.successpoints at color.functional.success.dark
--wsu-on-success#FFFFFFsemantic.light.status.success-onpoints at color.white
--wsu-warning#634D00semantic.light.status.warningpoints at color.functional.warning.dark
--wsu-on-warning#FFFFFFsemantic.light.status.warning-onpoints at color.white
--wsu-info#0E5A7Dsemantic.light.status.infopoints at color.functional.info.dark
--wsu-on-info#FFFFFFsemantic.light.status.info-onpoints at color.white
--wsu-nonproduction#8B2145semantic.light.status.nonproductionpoints at color.functional.nonproduction.dark
--wsu-on-nonproduction#FFFFFFsemantic.light.status.nonproduction-onpoints at color.white

The same names, resolved against the dark surfaces. A component that uses the semantic layer needs no dark-mode branch of its own.

Semantic color, dark: 46 tokens, generated from packages/tokens/dist/tokens.json
CSS custom propertyValueToken
--wsu-surface#142521semantic.dark.surface.basepoints at color.ink.900
--wsu-surface-sunken#0A1916semantic.dark.surface.sunkenpoints at color.ink.950
--wsu-surface-raised#1F322Esemantic.dark.surface.raisedpoints at color.ink.800
--wsu-surface-overlay#1F322Esemantic.dark.surface.overlaypoints at color.ink.800
--wsu-surface-inverse#f4f4f5semantic.dark.surface.inversepoints at color.neutral.100
--wsu-text#f4f4f5semantic.dark.text.basepoints at color.neutral.100
--wsu-text-muted#d4d4d8semantic.dark.text.mutedpoints at color.neutral.300
--wsu-text-subtle#a1a1aasemantic.dark.text.subtlepoints at color.neutral.400
--wsu-text-on-inverse#142521semantic.dark.text.on-inversepoints at color.ink.900
--wsu-brand#cedddbsemantic.dark.brand.basepoints at color.green.50
--wsu-brand-hover#F2F7F6semantic.dark.brand.hoverpoints at color.green.25
--wsu-brand-active#FFFFFFsemantic.dark.brand.activepoints at color.white
--wsu-brand-subtle#062b27semantic.dark.brand.subtlepoints at color.green.800
--wsu-on-brand#0A1916semantic.dark.brand.onpoints at color.ink.950
--wsu-accent#ffd65csemantic.dark.accent.basepoints at color.gold.300
--wsu-accent-subtle#a48111semantic.dark.accent.subtlepoints at color.gold.900
--wsu-on-accent#0A1916semantic.dark.accent.onpoints at color.ink.950
--wsu-border#314842semantic.dark.border.basepoints at color.ink.700
--wsu-border-strong#a1a1aasemantic.dark.border.strongpoints at color.neutral.400
--wsu-border-brand#cedddbsemantic.dark.border.brandpoints at color.green.50
--wsu-focus-ring#ffd65csemantic.dark.focus.ringpoints at color.gold.300
--wsu-focus-offset#09090bsemantic.dark.focus.offsetpoints at color.neutral.950
--wsu-chrome-masthead#094038semantic.dark.chrome.mastheadpoints at color.green.600
--wsu-chrome-masthead-text#FFFFFFsemantic.dark.chrome.masthead-textpoints at color.white
--wsu-chrome-masthead-muted#F2F7F6semantic.dark.chrome.masthead-mutedpoints at color.green.25
--wsu-chrome-bar#0c5449semantic.dark.chrome.barpoints at color.green.400
--wsu-chrome-bar-hover#0b4a40semantic.dark.chrome.bar-hoverpoints at color.green.500
--wsu-chrome-bar-text#FFFFFFsemantic.dark.chrome.bar-textpoints at color.white
--wsu-chrome-bar-muted#F2F7F6semantic.dark.chrome.bar-mutedpoints at color.green.25
--wsu-chrome-on-chrome-focus#ffd65csemantic.dark.chrome.on-chrome-focuspoints at color.gold.300
--wsu-chrome-mark#FFFFFFsemantic.dark.chrome.markpoints at color.white
--wsu-mockup-device#09090bsemantic.dark.mockup.devicepoints at color.black
--wsu-mockup-bezel#71717asemantic.dark.mockup.bezelpoints at color.neutral.500
--wsu-mockup-panel#050D0Bsemantic.dark.mockup.panelpoints at color.ink.975
--wsu-mockup-screen#18181bsemantic.dark.mockup.screenpoints at color.neutral.900
--wsu-on-mockup#f4f4f5semantic.dark.mockup.onpoints at color.neutral.100
--wsu-danger#FFB4ABsemantic.dark.status.dangerpoints at color.functional.danger.light
--wsu-on-danger#0A1916semantic.dark.status.danger-onpoints at color.ink.950
--wsu-success#8FE0AEsemantic.dark.status.successpoints at color.functional.success.light
--wsu-on-success#0A1916semantic.dark.status.success-onpoints at color.ink.950
--wsu-warning#FFD65Csemantic.dark.status.warningpoints at color.functional.warning.light
--wsu-on-warning#0A1916semantic.dark.status.warning-onpoints at color.ink.950
--wsu-info#8CC8E8semantic.dark.status.infopoints at color.functional.info.light
--wsu-on-info#0A1916semantic.dark.status.info-onpoints at color.ink.950
--wsu-nonproduction#F5A3B8semantic.dark.status.nonproductionpoints at color.functional.nonproduction.light
--wsu-on-nonproduction#0A1916semantic.dark.status.nonproduction-onpoints at color.ink.950

Layout

A 4px scale. Use these rather than arbitrary lengths so gaps line up across components.

Spacing: 14 tokens, generated from packages/tokens/dist/tokens.json
CSS custom propertyValueToken
--wsu-space-00remspace.0
--wsu-space-10.25remspace.1
--wsu-space-20.5remspace.2
--wsu-space-30.75remspace.3
--wsu-space-41remspace.4
--wsu-space-51.25remspace.5
--wsu-space-61.5remspace.6
--wsu-space-82remspace.8
--wsu-space-102.5remspace.10
--wsu-space-123remspace.12
--wsu-space-164remspace.16
--wsu-space-205remspace.20
--wsu-space-246remspace.24
--wsu-space-px0.0625remspace.px

Three named roles rather than a size scale: field, box and selector. daisyUI reads the same three names, so its controls round the way ours do.

Radius: 6 tokens, generated from packages/tokens/dist/tokens.json
CSS custom propertyValueToken
--wsu-radius-none0remradius.none
--wsu-radius-sm0.25remradius.sm
--wsu-radius-field0.375remradius.field
--wsu-radius-box0.75remradius.box
--wsu-radius-selector0.5remradius.selector
--wsu-radius-full9999remradius.full

Layout and control dimensions. size.tap-min is 44px, which is WCAG 2.2 Target Size (AAA, 2.5.5); size.tap-compact is the 24px AA floor (2.5.8) for controls in a dense table.

Sizes: 11 tokens, generated from packages/tokens/dist/tokens.json
CSS custom propertyValueToken
--wsu-size-tap-min2.75remsize.tap-min
--wsu-size-tap-compact2.25remsize.tap-compact
--wsu-size-focus-ring-width0.1875remsize.focus-ring-width
--wsu-size-focus-ring-offset0.125remsize.focus-ring-offset
--wsu-size-border-width0.0625remsize.border-width
--wsu-size-border-width-strong0.125remsize.border-width-strong
--wsu-size-content-max75remsize.content-max
--wsu-size-gutter0.9375remsize.gutter
--wsu-size-prose-max42remsize.prose-max
--wsu-size-sidebar17remsize.sidebar
--wsu-size-sidebar-collapsed4remsize.sidebar-collapsed

Matching Tailwind, so a utility class and a WayneUI component break at the same width.

Breakpoints: 5 tokens, generated from packages/tokens/dist/tokens.json
CSS custom propertyValueToken
--wsu-bp-xs20rembreakpoint.xs
--wsu-bp-sm40rembreakpoint.sm
--wsu-bp-md48rembreakpoint.md
--wsu-bp-lg64rembreakpoint.lg
--wsu-bp-xl80rembreakpoint.xl

Named layers, so an overlay that must sit above the drawer says so instead of guessing a number.

Stacking order: 6 tokens, generated from packages/tokens/dist/tokens.json
CSS custom propertyValueToken
--wsu-z-base0z.base
--wsu-z-sticky100z.sticky
--wsu-z-drawer200z.drawer
--wsu-z-overlay300z.overlay
--wsu-z-toast400z.toast
--wsu-z-skip-link500z.skip-link

Type, depth and motion

Stratium is the university display face, with Lato as the first fallback and a self-hosted Lato Fallback metric-matched to it so the swap does not reflow the page.

Typography: 24 tokens, generated from packages/tokens/dist/tokens.json
CSS custom propertyValueToken
--wsu-font-family-displayStratium 1, Lato, Lato Fallback, Calibri, Helvetica Neue, Arial, sans-seriffont.family.display
--wsu-font-family-bodyLato, Lato Fallback, Calibri, Helvetica Neue, Arial, sans-seriffont.family.body
--wsu-font-family-monoui-monospace, SFMono-Regular, Menlo, Consolas, Liberation Mono, monospacefont.family.mono
--wsu-font-weight-light300font.weight.light
--wsu-font-weight-regular400font.weight.regular
--wsu-font-weight-bold700font.weight.bold
--wsu-font-weight-black900font.weight.black
--wsu-font-size-xs0.75remfont.size.xs
--wsu-font-size-sm0.875remfont.size.sm
--wsu-font-size-base1remfont.size.base
--wsu-font-size-lg1.125remfont.size.lg
--wsu-font-size-xl1.25remfont.size.xl
--wsu-font-size-2xl1.5remfont.size.2xl
--wsu-font-size-3xl1.875remfont.size.3xl
--wsu-font-size-4xl2.25remfont.size.4xl
--wsu-font-size-5xl3remfont.size.5xl
--wsu-font-lineHeight-tight1.2font.lineHeight.tight
--wsu-font-lineHeight-snug1.35font.lineHeight.snug
--wsu-font-lineHeight-base1.5font.lineHeight.base
--wsu-font-lineHeight-relaxed1.75font.lineHeight.relaxed
--wsu-font-letterSpacing-tight-0.015emfont.letterSpacing.tight
--wsu-font-letterSpacing-normal0emfont.letterSpacing.normal
--wsu-font-letterSpacing-wide0.025emfont.letterSpacing.wide
--wsu-font-letterSpacing-caps0.06emfont.letterSpacing.caps

Elevation. Depth is never the only signal for a state, because a shadow is invisible in forced colors.

Shadow: 5 tokens, generated from packages/tokens/dist/tokens.json
CSS custom propertyValueToken
--wsu-shadow-nonenoneshadow.none
--wsu-shadow-sm0 1px 2px 0 rgb(16 18 18 / 0.06)shadow.sm
--wsu-shadow-md0 2px 4px -1px rgb(16 18 18 / 0.08), 0 1px 2px -1px rgb(16 18 18 / 0.06)shadow.md
--wsu-shadow-lg0 8px 16px -4px rgb(16 18 18 / 0.10), 0 2px 4px -2px rgb(16 18 18 / 0.06)shadow.lg
--wsu-shadow-overlay0 16px 40px -8px rgb(16 18 18 / 0.24)shadow.overlay

Two working durations: fast for hovers, presses and small reveals, base for anything entering or leaving. prefers-reduced-motion collapses both globally in base.css, which only works because no component invents its own timing.

Motion: 4 tokens, generated from packages/tokens/dist/tokens.json
CSS custom propertyValueToken
--wsu-duration-instant0msduration.instant
--wsu-duration-fast120msduration.fast
--wsu-duration-base200msduration.base
--wsu-duration-slow320msduration.slow

Consuming them

bash
pnpm add @waynestate/wayne-ui-tokens
js
import tokens from '@waynestate/wayne-ui-tokens'
import { contrastRatio, WCAG } from '@waynestate/wayne-ui-tokens/contrast'
css
@import "@waynestate/wayne-ui-tokens/css";
scss
@use "@waynestate/wayne-ui-tokens/scss";

@waynestate/wayne-ui-tokens/json is the manifest this page is built from, including the contrast declarations. If you are writing a color check of your own, use the exported contrastRatio rather than reimplementing the maths: the token tests, the component tests, this site and your application then all compute it the same way instead of each being slightly wrong in a different direction.

You almost never need the tokens package directly. wayne-ui.css already contains the whole token layer, unlayered, so var(--wsu-brand) works in any stylesheet loaded after it.