Skip to content

BYO Tokens — Bring Your Own Tokens

BYO Tokens (Bring Your Own Tokens) lets a project keep a product-owned CSS namespace (for example --acme-*) next to Airframe’s --af-* contract. Patterns still read --af-* only. Your tokens can export to Figma, bridge into Airframe, or stay app-only.

Three integration paths:

PathDirectionConfigExport
BYO tokensProduct namespace (--acme-*)theme.byoTokens.prefixDTCG + Figma
Bridge into Airframe--acme-*--af-*in theme.filesAirframe + bridge metadata
Airframe → existing UI--af-* → host (--ion-*, …)theme.existingUiNone (runtime only)

Day-to-day Airframe theming stays on Theming. Export commands live on Theme package. Plugin how-to: Figma plugin. Product overview: Airframe → Figma.

Use BYO Tokens when the product needs tokens Airframe does not own:

  • Gradients, elevation, display type, marketing-only scales
  • A brand namespace you want in Figma Dev Mode as --acme-*, not rewritten to --af-*
  • Ionic or other UI chrome that should follow Airframe look without polluting the Figma export

Use extension tokens (extra --af-* not in the catalog) when the name can stay in Airframe’s namespace. See Extra tokens.

Add byoTokens to airframe.config.js. Slug and Figma collection prefix derive from prefix (--acme-acme, Acme /).

/** @type {import('@airframeui/build').AirframeBuildOptions} */
export default {
  theme: {
    files: ['./src/tokens.css', './src/brands/*.css'],
    outputDir: '.airframeui',
    byoTokens: {
      prefix: '--acme-',
      // optional — default: title-cased slug → 'Acme / '
      // collectionPrefix: 'Acme Corp / ',
    },
    existingUi: {
      excludePrefixes: ['--ion-'],
      files: ['./src/existing-ui/ionic-airframe.scss'],
    },
  },
};
KeyWhat it does
theme.byoTokens.prefixRequired to capture BYO vars from theme.files. Must start with -- and end with -. Cannot be --af-.
theme.byoTokens.collectionPrefixOptional Figma collection label. Default derived from prefix.
theme.existingUi.excludePrefixesHost prefixes never captured as BYO (even if they appear in theme.files).
theme.existingUi.filesRuntime UI mapping sheets — informational for lint; not exported.

Without byoTokens.prefix, behaviour is unchanged: non---af-* variables are reported as unmapped.

Put BYO tokens and bridges in theme.files. Keep Airframe → existing UI mapping outside that list.

/* src/tokens.css — in theme.files */

:root {
  /_ BYOexported as-is under Acme / in Figma _/
  --acme-gradient-hero: linear-gradient(135deg, #0876dd 0%, #6b7280 100%);
  --acme-elevation-2: 0 4px 12px rgb(0 0 0 / 12%);
  --acme-display-font: 'Acme Display', system-ui, sans-serif;

  /_ Mode companionssame --light / --dark / --hc-_ rules as --af-* */
  --acme-brand--light: #c23018;
  --acme-brand--dark: #e85d2a;

  /_ Bridge into Airframepatterns follow brand _/
  --af-base-primary--light: var(--acme-brand--light);
  --af-base-primary--dark: var(--acme-brand--dark);
  --af-font-heading: var(--acme-display-font);
}
/* src/existing-ui/ionic-airframe.scss — NOT in theme.files */

:root {
--ion-color-primary: var(--af-color-primary);
--ion-background-color: var(--af-color-background);
}

Load order at runtime: Airframe core → BYO / bridge CSS → existing UI mapping.

Brands use the same BYO prefix with [data-brand] — separate export palettes, not separate prefixes.

Both commands pick up BYO when byoTokens.prefix is set:

npx af theme export              # tokens/ + byo/<slug>/ + figma.variables.json
npx af theme export --no-figma   # DTCG only

Output layout:

.airframeui/
tokens/default/light.tokens.json     ← Airframe
byo/acme/default/light.tokens.json   ← BYO (slug from prefix)
figma.variables.json                 ← both collection families + bridges[]

In Figma, Update Figma creates or updates both Airframe / default and Acme / default. BYO variables keep Dev Mode WEB syntax as --acme-*.

Working examples in the repo:

  • examples/byotokens — BYO Tokens export (--demo-*, bridges, byo/demo/, Demo Figma collection)
  • examples/theme — multi-brand Airframe DTCG + Figma (no BYO Tokens)
  • examples/ionic — Ionic shell + existingUi mapping (see below)

Ionic apps keep native chrome on --ion-*. Map those from Airframe at runtime without polluting the Figma export.

Config — same byoTokens as above, plus existingUi.files for the mapping sheet:

/** @type {import('@airframeui/build').AirframeBuildOptions} */
export default {
  theme: {
    files: ['./src/styles/tokens.css'],
    byoTokens: { prefix: '--demo-' },
    existingUi: {
      excludePrefixes: ['--ion-'],
      files: ['./src/styles/existing-ui/ionic-airframe.css'],
    },
  },
};

Runtime load order (see examples/ionic/src/main.ts):

  1. Ionic core CSS
  2. Airframe core CSS
  3. tokens.css — BYO + bridges (in theme.files)
  4. ionic-airframe.css--af-*--ion-* (not exported)
/* src/styles/existing-ui/ionic-airframe.css — NOT in theme.files */

:root {
  --ion-color-primary: var(--af-color-primary);
  --ion-color-primary-contrast: var(--af-color-on-primary);
  --ion-background-color: var(--af-color-background);
  --ion-toolbar-background: var(--af-color-surface-primary);
  /_ …full mapping in examples/ionic _/
}

Run the demo page from the repo root:

pnpm example:ionic   # http://localhost:5175

Ionic toolbar, tabs, and ion-button should track the same primary/secondary colours as Airframe af-btn patterns.

npx af theme lint ./src/tokens.css
CodeMeaning
byo-tokenBYO var will export under your product collection
byo-bridge--af-*var(--acme-*) recorded
byo-unmapped-sourceBridge references a BYO var not declared in theme.files
existing-ui-in-theme-filesHost prefix (e.g. --ion-*) belongs in existingUi.files, not export
  • Patterns consume --af-* only. Bridge brand colours into Airframe when UI should follow them.
  • One BYO prefix per project. Brand variation is [data-brand], not --delta-* vs --vortex-*.
  • Do not duplicate semantics in two namespaces forever — bridge once, or pick BYO for inventory-only tokens.
  • Extension tokens (--af-chart-1) are still extra --af-* names. BYO is a separate namespace.