Skip to content

Tokens

Override hex primitives in :root:

:root {
  --af-base-primary: #018183;
}

Interaction is derived via color-mix():

:root {
  --af-hover-mix: 12%;
  --af-active-mix: 18%;
}

For dark mode and theme overrides, see Theming.

Airframe ships with a calm, modern default palette designed for product UI.

Neutrals form the structure—they provide the foundation for surfaces, borders, and text. Accent colors indicate motion and intent—they signal actions, states, and meaning.

Everything is overridable. Override primitives in :root to change the entire system, or adjust semantic tokens for targeted changes.

:root {
  /* Accent colors */
  --af-base-primary: #4663f5;
  --af-base-secondary: #22d3ee;
  --af-base-tertiary: #111827;
  --af-base-success: #22c55e;
  --af-base-warning: #fbbf24;
  --af-base-danger: #dc2626;
  --af-base-info: #38bdf8;

  /_ Neutrals _/
  --af-base-white: #ffffff;
  --af-base-black: #000000;
  --af-base-neutral-50: #f8fafc;
  --af-base-neutral-100: #f1f5f9;
  --af-base-neutral-200: #e2e8f0;
  --af-base-neutral-300: #cbd5e1;
  --af-base-neutral-400: #94a3b8;
  --af-base-neutral-500: #64748b;
  --af-base-neutral-600: #475569;
  --af-base-neutral-700: #334155;
  --af-base-neutral-800: #1f2937;
  --af-base-neutral-900: #111827;
}

These control how interaction states are derived via color-mix():

:root {
  --af-hover-mix: 12%;      /* Hover state color mix */
  --af-active-mix: 18%;     /* Active state color mix */
  --af-soft-mix: 12%;       /* Soft variant color mix (lighter) */
  --af-hard-mix: 16%;       /* Hard variant color mix (darker) */
  --af-border-mix: 22%;     /* Border color mix */
}

The action token (--af-color-action) is the semantic “main CTA/brand color” used for buttons, links, focus rings, and other interactive elements. It defaults to primary; --af-color-primary (and --af-color-on-primary) are resolved from action, so all existing components that use primary automatically respect the override.

Override action per theme when you want a different accent in dark or high-contrast modes. For high-contrast themes, ensure colors meet WCAG 2.2 AA contrast requirements (4.5:1 for normal text, 3:1 for large text):

/* Dark mode */
@media (prefers-color-scheme: dark) {
  :root {
    --af-color-action--dark: #60a5fa;
    --af-color-on-action--dark: #0f172a;
  }
}

/_ Or when using a manual dark theme class _/
[data-theme="dark"], .dark {
  --af-color-action--dark: #60a5fa;
  --af-color-on-action--dark: #0f172a;
}

/_ High-contrast light - use stronger colors for better contrast _/
:root {
  --af-color-action--hc-light: #0066cc; /_ Stronger blue meets WCAG AA _/
  --af-color-on-action--hc-light: #ffffff;
}

/_ High-contrast dark - lighter colors for dark backgrounds _/
:root {
  --af-color-action--hc-dark: #60a5fa; /_ Lighter blue for dark mode _/
  --af-color-on-action--hc-dark: #000000;
}

Note: High-contrast overrides work automatically with system prefers-contrast: more or prefers-contrast: high preference. No media queries needed—just define the --hc-light and --hc-dark tokens in :root. See High Contrast Theming for complete examples.

Derived tokens (--af-color-action-hover, -action-active, -action-soft, -action-hard) are computed from the resolved action via color-mix(), so they stay consistent when you override.

Typography is driven by semantic tokens so brands can override safely:

:root {
  /* Font primitives */
  --af-base-font-sans: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, "Apple Color Emoji", "Segoe UI Emoji";
  --af-base-font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;

  /_ Semantic font tokens _/
  --af-font-primary: var(--af-base-font-sans); /_ Primary font (used for body text by default) _/
  --af-font-heading: var(--af-font-primary); /_ Heading font (applied to h1-h6, .af-text-h_, .af-text-display) _/
  --af-font-body: var(--af-base-font-sans); /_ Body text font _/
  --af-font-code: var(--af-base-font-mono); /_ Code font _/
  --af-font-mono: var(--af-base-font-mono); /_ Monospace font */
}

Customizing fonts:

:root {
  /* Use custom web fonts */
  --af-font-primary: 'Inter', 'Roboto', sans-serif;
  --af-font-heading: 'Playfair Display', serif;  /* Different font for headings */
  --af-font-body: var(--af-font-primary);        /* Body uses primary */
}
:root {
  /* Core type scale */
  --af-text-xs: 0.75rem;
  --af-text-sm: 0.875rem;
  --af-text-md: 1rem;
  --af-text-lg: 1.125rem;
  --af-text-xl: 1.25rem;
  --af-text-2xl: 1.5rem;
  --af-text-3xl: 1.875rem;

  /_ Leading (line-height) - typography term for vertical spacing between lines _/
  /_ Use --af-leading-_ tokens; these map to CSS line-height property */
  --af-leading-tight: 1.1;
  --af-leading-normal: 1.5;
  --af-leading-loose: 1.7;

  /_ Weights _/
  --af-weight-regular: 400;
  --af-weight-medium: 500;
  --af-weight-semibold: 600;
  --af-weight-bold: 700;
  --af-weight-extrabold: 800;
}

Typography role tokens map semantic roles (h1, h2, body, etc.) to type scale values. Override these to customize typography without touching primitives:

:root {
  /* Display type */
  --af-type-display-size: var(--af-text-3xl);
  --af-type-display-weight: var(--af-weight-bold);
  --af-type-display-leading: var(--af-leading-tight);

  /_ Heading sizes _/
  --af-type-h1-size: var(--af-text-3xl);
  --af-type-h2-size: var(--af-text-2xl);
  --af-type-h3-size: var(--af-text-xl);
  --af-type-h4-size: var(--af-text-lg);
  --af-type-h5-size: var(--af-text-md);
  --af-type-h6-size: var(--af-text-sm);

  /_ Heading weights _/
  --af-type-h1-weight: var(--af-weight-semibold);
  --af-type-h2-weight: var(--af-weight-semibold);
  --af-type-h3-weight: var(--af-weight-semibold);
  --af-type-h4-weight: var(--af-weight-semibold);
  --af-type-h5-weight: var(--af-weight-semibold);
  --af-type-h6-weight: var(--af-weight-semibold);

  /_ Heading leading _/
  --af-type-h1-leading: var(--af-leading-tight);
  --af-type-h2-leading: var(--af-leading-tight);
  --af-type-h3-leading: var(--af-leading-tight);
  --af-type-h4-leading: var(--af-leading-normal);
  --af-type-h5-leading: var(--af-leading-normal);
  --af-type-h6-leading: var(--af-leading-normal);

  /_ Heading letter spacing _/
  --af-type-h1-tracking: -0.02em;
  --af-type-h2-tracking: -0.01em;
  --af-type-h3-tracking: 0em;
  --af-type-h4-tracking: 0em;
  --af-type-h5-tracking: 0em;
  --af-type-h6-tracking: 0em;

  /_ Body and caption _/
  --af-type-body-size: var(--af-text-md);
  --af-type-body-weight: var(--af-weight-regular);
  --af-type-body-leading: var(--af-leading-normal);
  --af-type-caption-size: var(--af-text-sm);
  --af-type-caption-weight: var(--af-weight-regular);
  --af-type-caption-leading: var(--af-leading-normal);

  /_ Semantic typography roles _/
  --af-text-body-size: var(--af-text-md);
  --af-text-body-leading: var(--af-leading-normal);
  --af-text-heading-leading: var(--af-leading-tight);
  --af-text-heading-weight: var(--af-weight-semibold);
}

The space scale provides consistent spacing values used throughout the system:

:root {
  --af-space-0: 0px;
  --af-space-1: 4px;
  --af-space-2: 8px;
  --af-space-3: 12px;
  --af-space-4: 16px;
  --af-space-5: 24px;
  --af-space-6: 32px;
  --af-space-7: 40px;
  --af-space-8: 48px;
}

These tokens are used by spacing utilities (af-p-*, af-m-*, af-gap-*) and layout recipes.

Radius tokens control the roundness of corners:

:root {
  --af-radius-0: 0px;
  --af-radius-sm: 6px;
  --af-radius-md: 10px;
  --af-radius-lg: 16px;
  --af-radius-xl: 24px;
  --af-radius-full: 9999px;
}

Breakpoint tokens define responsive breakpoints used by responsive suffixes (@md, @lg, etc.):

:root {
  --af-bp-xs: 360px;
  --af-bp-sm: 640px;
  --af-bp-md: 768px;
  --af-bp-lg: 1024px;
  --af-bp-xl: 1280px;
  --af-bp-2xl: 1536px;
}

Shadow tokens provide consistent elevation:

:root {
  --af-shadow-sm: 0 1px 2px rgba(0,0,0,.06);
  --af-shadow-md: 0 6px 16px rgba(0,0,0,.10);
  --af-shadow-lg: 0 16px 40px rgba(0,0,0,.14);
}

Z-index tokens provide a consistent layering system:

:root {
  --af-z-base: 0;
  --af-z-dropdown: 1000;
  --af-z-sticky: 1100;
  --af-z-overlay: 1200;
  --af-z-modal: 1300;
  --af-z-toast: 1400;
}

Border tokens control border width and hit target sizes:

:root {
  --af-border-width: 2px;
  --af-hit-size: 2.75rem;  /* Minimum touch target size */
}

Layout tokens control container widths and layout spacing:

:root {
  --af-layout-gap: var(--af-space-4);
  --af-container-padding-x: var(--af-space-4);
  --af-container-max-w: 90em;        /* 1440px - default container */
  --af-container-max-w-xs: 25rem;   /* 400px */
  --af-container-max-w-sm: 40rem;   /* 640px */
  --af-container-max-w-md: 48rem;   /* 768px */
  --af-container-max-w-lg: 64rem;   /* 1024px */
  --af-container-max-w-xl: 80rem;   /* 1280px */
  --af-container-max-w-2xl: 96rem; /* 1536px */
}

Component tokens are a thin, override-friendly layer for common sizing tweaks. They sit on top of global tokens so you can adjust buttons and form controls without rewriting styles.

:root {
  --af-control-min-height: auto;
  --af-control-radius: var(--af-radius-xl);
  --af-control-padding-y: var(--af-space-2);
  --af-control-padding-x: var(--af-space-3);
  --af-control-border: var(--af-color-border);
}
:root {
  /* Button sizing */
  --af-btn-min-height: var(--af-control-min-height);
  --af-btn-radius: var(--af-radius-full);
  --af-btn-padding-y: var(--af-control-padding-y);
  --af-btn-padding-x: var(--af-space-4);
  --af-btn-gap: var(--af-space-2);
  --af-btn-icon-size: 1.25em;
  --af-btn-font-weight: var(--af-weight-medium);
  --af-btn-border-width: var(--af-border-width);

  /_ Button colors (primary/default) _/
  --af-btn-default-bg: var(--af-color-primary);
  --af-btn-default-color: var(--af-color-on-primary);
  --af-btn-default-hover: var(--af-color-primary-hover);
  --af-btn-default-active: var(--af-color-primary-active);

  /_ Button colors (secondary) _/
  --af-btn-secondary-bg: var(--af-base-secondary);
  --af-btn-secondary-color: var(--af-color-on-secondary);
  --af-btn-secondary-hover: color-mix(in srgb, var(--af-btn-secondary-bg), black 4%);
  --af-btn-secondary-active: color-mix(in srgb, var(--af-btn-secondary-bg), black 8%);

  /_ Button colors (outline) _/
  --af-btn-outline-color: var(--af-color-primary);
  --af-btn-outline-border: var(--af-color-primary);
  --af-btn-outline-hover: var(--af-btn-outline-border);
  --af-btn-outline-active: color-mix(in srgb, var(--af-color-primary), var(--af-color-surface-primary) 84%);
}
:root {
  --af-input-min-height: var(--af-control-min-height);
  --af-input-radius: var(--af-control-radius);
  --af-input-padding-y: var(--af-control-padding-y);
  --af-input-padding-x: var(--af-control-padding-x);
  --af-input-font-size: var(--af-text-md);
  --af-input-leading: var(--af-leading-normal);
  --af-input-placeholder-color: var(--af-color-text-muted);
}
:root {
  --af-link-primary: var(--af-color-primary);
  --af-link-hover-primary: var(--af-color-primary-hover);
}
:root {
  --af-card-radius: var(--af-radius-lg);
  --af-card-padding: var(--af-space-4);
  --af-card-shadow: var(--af-shadow-sm);
  --af-card-shadow-hover: var(--af-shadow-md);
}
:root {
  --af-focus-ring-color: var(--af-color-focus);
  --af-focus-ring-width: 2px;
  --af-focus-ring-offset: 2px;
}
:root {
  --af-control-min-height: 3rem;
  --af-btn-gap: 0.75rem;
  --af-input-placeholder-color: rgba(0, 0, 0, 0.45);
}

Motion tokens provide consistent timing for transitions and animations, with automatic reduced motion support built-in.

Transition durations (for CSS transitions):

:root {
  --af-duration-fast: 120ms;      /* Quick interactions */
  --af-duration-md: 200ms;        /* Standard transitions */
  --af-duration-slow: 320ms;      /* Deliberate changes */
}

Animation durations (for keyframe animations):

:root {
  --af-animation-duration-fast: 200ms;   /* Quick animations */
  --af-animation-duration-md: 400ms;      /* Standard animations */
  --af-animation-duration-slow: 600ms;    /* Slower animations */
  --af-animation-duration-slower: 1500ms; /* Very slow animations */
}
:root {
  --af-ease-standard: cubic-bezier(.2, .0, .2, 1);    /* Default easing */
  --af-ease-emphasized: cubic-bezier(.2, .0, 0, 1);   /* More pronounced */
  --af-ease-in: cubic-bezier(.4, .0, 1, 1);           /* Ease in */
  --af-ease-out: cubic-bezier(.0, .0, .2, 1);         /* Ease out */
  --af-ease-in-out: cubic-bezier(.4, .0, .2, 1);      /* Ease in-out */
  --af-ease-linear: linear;                            /* Linear */
}

Transitions:

/* Button hover transition */
.af-btn {
  transition: background-color var(--af-duration-fast) var(--af-ease-standard);
}

/_ Card shadow transition _/
.af-card {
  transition: box-shadow var(--af-duration-md) var(--af-ease-out);
}

/_ Multiple properties _/
.element {
  transition:
  background-color var(--af-duration-fast) var(--af-ease-standard),
  transform var(--af-duration-md) var(--af-ease-emphasized);
}

Animations:

/* Spinner animation */
@keyframes spin {
  from { transform: rotate(0deg); }
  to { transform: rotate(360deg); }
}

.af-spinner {
  animation: spin var(--af-animation-duration-md) var(--af-ease-linear) infinite;
}

/_ Skeleton loading animation _/
@keyframes skeleton-load {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.5; }
}

.af-skeleton {
  animation: skeleton-load var(--af-animation-duration-slower) var(--af-ease-in-out) infinite;
}

All motion tokens automatically respect the user’s prefers-reduced-motion preference. When reduced motion is enabled, all durations are set to 0.01ms (effectively instant).

No additional code needed - just use the tokens:

/* This automatically respects reduced motion */
.button {
  transition: background-color var(--af-duration-fast) var(--af-ease-standard);
}

/_ This also respects reduced motion _/
.spinner {
  animation: spin var(--af-animation-duration-md) linear infinite;
}

Customizing motion tokens:

:root {
  /* Override default durations */
  --af-duration-fast: 100ms;
  --af-animation-duration-slow: 800ms;

  /_ Custom easing _/
  --af-ease-standard: cubic-bezier(0.4, 0, 0.2, 1);
}

Note: The reduced motion override still applies even when you customize these values, ensuring accessibility is maintained.