Design tokens and theming strategies in TypeScript for Mac-like UIs across platforms
Build a TypeScript-first design token system for Mac-like UIs across web, desktop and mobile—typed tokens, runtime theming, and cross-platform consistency.
Mac-like UIs across platforms: the pain and the promise
Shipping consistent, Mac-like aesthetics across web, desktop and mobile is deceptively hard. Teams wrestle with duplicated palettes, inconsistent spacing, divergent icon sets and brittle runtime theming. Components that look right on macOS can feel off on Electron, React Native or the web—because tokens were never the single source of truth.
This guide walks through an advanced, TypeScript-first design token system (colors, spacing, icons) that preserves the Mac-like visual language, supports runtime theme switching, and gives you typed tokens in your component library—across platforms in 2026.
Quick summary — what you’ll get
- Typed tokens generated and enforced in TypeScript so components can only consume valid token names.
- Runtime theming that works on web (CSS variables), desktop (Electron/Tauri), and mobile (React Native / native bridges).
- Platform overrides and deep merging with preserved type-safety.
- Icon mapping as a typed enum that maps to SF-like symbols or SVG assets per platform.
- Implementation patterns, CI steps and accessibility tips tailored for Mac-like aesthetics in 2026.
Why TypeScript-first tokens matter in 2026
In late 2025 and early 2026 we’ve seen stronger momentum behind runtime token pipelines and typed tokens. Tooling such as Style Dictionary matured, Figma export workflows became more robust, and TypeScript's 5.x series added inference improvements that make preserving literal types much easier. The result: teams can reliably ship a single token source and consume it in JS/TS without losing type-safety.
Design principles for Mac-like UIs
- Atomic tokens: base scales (colors, spacing, radii, type sizes).
- Semantic tokens: surface, text, accent, border built from atomic tokens—so visuals can change without touching components.
- Platform sensibility: transparency/vibrancy look great on macOS; fallback behavior must exist for platforms that don’t support native blur.
- Runtime override: allow user-driven light/dark plus app-level themes that preserve Mac-like proportions.
Token model: atomic + semantic + platform overrides
Build tokens in three layers:
- Atomic JSON (single source of truth exported from design tooling or authored by your design system team).
- Generated TypeScript artifacts (tokens.ts / tokens.d.ts) so apps import typed tokens directly.
- Runtime merges that combine base tokens with platform or user overrides and expose CSS vars / native bridges.
Example token shape (JSON)
{
"atomic": {
"color": {
"blue-50": "#EAF3FF",
"blue-500": "#0A66FF",
"gray-900": "#0B0B0B"
},
"space": {
"xs": 4,
"sm": 8,
"md": 16,
"lg": 24
},
"radius": {
"sm": 6,
"md": 12
},
"icon": {
"chevron-right": "chevron-right",
"sidebar": "sidebar"
}
},
"semantic": {
"surface": "color.blue-50",
"text-primary": "color.gray-900",
"accent": "color.blue-500"
}
}
TypeScript: preserve literal types and derive a Tokens type
Author tokens in a TypeScript file and export them as const. TypeScript will keep literal values and let you derive strongly-typed helpers.
// tokens.ts
export const tokens = {
atomic: {
color: {
"blue-50": "#EAF3FF",
"blue-500": "#0A66FF",
"gray-900": "#0B0B0B",
},
space: { xs: 4, sm: 8, md: 16, lg: 24 },
radius: { sm: 6, md: 12 },
icon: { "chevron-right": "chevron-right", sidebar: "sidebar" }
},
semantic: {
surface: "atomic.color.blue-50",
"text-primary": "atomic.color.gray-900",
accent: "atomic.color.blue-500",
}
} as const;
export type Tokens = typeof tokens;
The as const assertion ensures token keys are literal. You can then build utility types that fetch values safely and validate references at compile time.
Typed token access helpers
Create a helper to resolve semantic tokens to atomic values at runtime while retaining type-safety for token keys.
type AtomicColorKeys = keyof Tokens['atomic']['color'];
type SemanticKey = keyof Tokens['semantic'];
// Resolve a semantic token path like "atomic.color.blue-500" to its value
function resolveSemantic(tokens: Tokens, semanticKey: SemanticKey) {
const path = tokens.semantic[semanticKey] as string; // e.g. "atomic.color.blue-50"
const parts = path.split('.');
// runtime resolution (simple; consider a safer resolver in production)
// (ts-ignore used to avoid deep indexing typing complexity)
// @ts-ignore
let cur: any = tokens;
for (const p of parts) cur = cur[p];
return cur as string | number;
}
// Usage
const bg = resolveSemantic(tokens, 'surface');
Keep this resolver small and deterministic. For complex pipelines use a compiled resolver generated during your build that replaces string paths with direct pointers—this also improves runtime performance.
Runtime theming strategies per platform
Web (CSS variables + container queries)
Use CSS variables as your runtime surface on the web. Generate a :root block for each theme and swap variables at runtime by toggling a class or updating variables directly.
// web-theme.ts - convert tokens to CSS vars
function tokensToCssVars(t: Tokens) {
const vars: Record = {};
for (const k of Object.keys(t.atomic.color)) {
vars[`--color-${k}`] = (t.atomic.color as any)[k];
}
for (const k of Object.keys(t.atomic.space)) {
vars[`--space-${k}`] = String((t.atomic.space as any)[k] + 'px');
}
return vars;
}
function applyCssVars(vars: Record, root: HTMLElement = document.documentElement) {
for (const [k, v] of Object.entries(vars)) root.style.setProperty(k, v);
}
For modern, responsive Mac-like UI, consider using CSS container queries (now widely available by 2026) to adapt component spacing and typography to container size without redefining tokens per breakpoint.
Desktop (Electron / Tauri)
Desktop apps typically render web views. Use the same CSS variable approach and expose native system color preferences (like macOS accent color or vibrancy preference) via the main process. Use a small runtime bridge that receives platform-level overrides and merges them into tokens before applying CSS vars.
Mobile (React Native / native)
React Native doesn't have CSS vars; use a typed ThemeProvider that exposes tokens as constants. For performance, memoize resolved values and avoid passing full token objects down the tree. For native platforms, export tokens as JSON and load them into Swift/Kotlin, preserving the same token names.
// React Native themed styles (example)
import React from 'react';
export const ThemeContext = React.createContext(null);
export function useTokens() {
const t = React.useContext(ThemeContext);
if (!t) throw new Error('useTokens must be used under ThemeProvider');
return t;
}
// Component
function Card() {
const t = useTokens();
return (
// pseudo-code: style props use typed token values
// borderRadius = t.atomic.radius.md
null
);
}
Merging platform overrides with type safety
You want to allow platform teams to tweak tokens (e.g., more translucency on macOS). Use a typed deepMerge that preserves the Tokens type.
function typedDeepMerge(base: T, over: U): T & U {
// simple runtime merge; keep types via generic return
const out: any = { ...base };
for (const k of Object.keys(over)) {
const v = (over as any)[k];
if (v && typeof v === 'object' && !Array.isArray(v)) out[k] = typedDeepMerge((base as any)[k] ?? {}, v);
else out[k] = v;
}
return out;
}
// Usage: tokens + macos overrides
const macOverrides = { atomic: { color: { 'blue-50': 'rgba(234,243,255,0.6)' } } } as const;
const macTokens = typedDeepMerge(tokens, macOverrides);
The function preserves the compile-time type shape. Keep runtime complexity low; avoid heavy transforms during animation frames.
Icons: typed mapping and platform-appropriate assets
Mac-like UI frequently uses SF-like symbols with consistent stroke/weight. Keep icons as tokens so components request them by name rather than importing assets directly.
// icons.ts (generated from your asset pipeline)
export const icons = {
'chevron-right': { web: '/icons/chevron-right.svg', native: 'chevron.right' },
sidebar: { web: '/icons/sidebar.svg', native: 'sidebar' }
} as const;
export type IconName = keyof typeof icons;
// Typed Icon component
type IconProps = { name: IconName; size?: number };
export function Icon({ name, size = 18 }: IconProps) {
const entry = icons[name];
// platform-specific rendering omitted
return null;
}
Automate the generation of this file from your asset pipeline (SVG folder or SF Symbols export). That ensures the enum always matches available assets and prevents runtime 404s.
Generating tokens.d.ts for global consumption
For monorepos and packages, emit a tokens.d.ts so TypeScript consumers get autocompletion without importing raw JSON. Two practical approaches:
- Keep tokens in tokens.ts and ship it in your package; tsc will generate .d.ts automatically when you enable
declaration: true. - Keep tokens as JSON; add a small build script that converts JSON into a tokens.d.ts file with exported types.
// simple build script (node) - write tokens.d.ts with inferred type
import fs from 'fs';
const json = JSON.parse(fs.readFileSync('tokens.json', 'utf8'));
const content = `export declare const tokens: ${JSON.stringify(json, null, 2)} as const;`;
fs.writeFileSync('dist/tokens.d.ts', content);
The above is straightforward; for better types you can use zod to validate tokens and then stringify inferred types with utility libraries. The most robust path: keep a canonical tokens.ts as 'as const' and let tsc generate the .d.ts—this preserves literal types precisely.
Component patterns: small, typed props and token-only styles
Design systems benefit when components accept token identifiers instead of raw colors or numbers. That keeps the contract explicit and ensures runtime theme changes automatically affect all consumers.
type ColorToken = keyof Tokens['atomic']['color'];
type ButtonProps = { bg?: ColorToken; radius?: keyof Tokens['atomic']['radius'] };
function Button({ bg = 'blue-500', radius = 'md' }: ButtonProps) {
// resolve token to actual value depending on platform
}
Small prop sets and token-based props make components simpler to theme and easier to scale.
Accessibility & mac-like aesthetics—don't forget contrast
Mac-like UIs often favor translucency and subtle contrast. That can harm legibility. Programmatically ensure every semantic color meets WCAG contrast thresholds. Use runtime checks when themes are created or merged.
// simple contrast check (luminance calculations omitted)
function meetsContrast(fg: string, bg: string) {
// implement luminance & ratio check
return true;
}
Expose tooling in your CI pipeline that validates new tokens for contrast and accessibility before merging.
Performance & packaging
- On the web, applying a single :root update with CSS variables is cheap—avoid per-component style recalculation.
- On mobile, keep tokens as plain objects; avoid re-creating token objects on every render to prevent re-renders.
- For desktop, when bridging native prefs, debounce updates that cascade across the UI.
CI, developer ergonomics and workflows
A reliable token pipeline needs automation:
- Export tokens from design tool (Figma Tokens plugin or similar) into canonical JSON checked into a tokens/ folder. See exports and tooling notes in the export playbook.
- On commits, run a validation job (contrast, missing references) and regenerate the TypeScript artifacts (tokens.ts, icons.ts, tokens.d.ts).
- Publish artifacts to your internal npm registry or as a monorepo package consumed by web/mobile/desktop projects.
Real-world patterns and pitfalls
From experience building cross-platform systems for teams shipping macOS-like apps and web portals, here are common pitfalls and how to avoid them:
- Pitfall: Designers change token names frequently. Fix: enforce a token migration process and deprecate names gradually.
- Pitfall: Ad-hoc color usage in components. Fix: lint rules that disallow hard-coded colors outside a /tokens folder.
- Pitfall: Icons diverge across platforms. Fix: generate icon maps from a canonical asset set and export typed IconName.
2026 trends & future-proofing your token system
Looking into 2026, expect these trends to shape token strategies:
- Runtime-first theming will be the norm: users expect dynamic theme switching with zero reload.
- Design token ecosystems (export plugins + CLI tools) matured in 2025—invest in automating import/export workflows.
- Improved typed tooling: TypeScript continues to improve type inference. Use TS generics and const assertions to preserve token shapes end-to-end; see notes on TypeScript tooling trends.
Actionable checklist (start shipping today)
- Create canonical tokens.json in your design repo and commit it.
- Write a small tokens.ts wrapper with
as constso tsc can emit typed artifacts. - Generate icons.ts from your SVG/SF assets and export IconName.
- Implement a tiny runtime ThemeManager that merges tokens and applies CSS vars or provides context on mobile.
- Add CI validations (contrast, token references, missing icon files).
- Introduce lint rules preventing hard-coded tokens in components.
Code reference: minimal runtime ThemeManager (web + native)
type ThemeListener = (tokens: Tokens) => void;
class ThemeManager {
private tokens: Tokens;
private listeners = new Set();
constructor(base: Tokens) {
this.tokens = base;
}
setTheme(overrides: Partial) {
this.tokens = typedDeepMerge(this.tokens, overrides as any);
this.emit();
// apply to web CSS vars
if (typeof document !== 'undefined') applyCssVars(tokensToCssVars(this.tokens));
}
onChange(cb: ThemeListener) {
this.listeners.add(cb);
return () => this.listeners.delete(cb);
}
getTokens() {
return this.tokens;
}
private emit() {
for (const l of this.listeners) l(this.tokens);
}
}
Closing thoughts
Building a Mac-like cross-platform UI in 2026 is less about pixel-matching and more about establishing a robust token pipeline, strong TypeScript guarantees and small runtime surfaces that adapt to platform capabilities. When tokens are typed, generated and validated, teams ship visual consistency with speed and confidence.
"Treat tokens as your contract—typed, validated and versioned. Then the UI becomes predictable across platforms."
Next steps — try the starter pattern
Start with a small spike: create tokens.ts using the patterns above, wire a ThemeManager to your app shell, and convert one component to accept token keys only. If you want, clone our starter repo (link in the docs) and adapt it for Electron, React Native or web.
If you're building a design system or migrating a large codebase, follow the checklist above and add automatic token validation in CI. Typed tokens reduce runtime surprises and make cross-platform, Mac-like UIs maintainable at scale.
Call to action
Want the starter repo, CI config snippets and a sample tokens.d.ts generator script? Subscribe to our TypeScript UI patterns newsletter or download the companion repo to get working examples for web, desktop and mobile in 2026.
Related Reading
- Designing for Headless CMS in 2026: Tokens, Nouns, and Content Schemas
- Modding Ecosystems & TypeScript Tooling in 2026
- Proxy Management Tools for Small Teams: Observability, Automation, and Compliance Playbook (2026)
- Review: PRTech Platform X — Workflow Automation for Small Agencies in 2026
- How to Read Japanese Trail Signs: Safety Phrases and Quick Translations
- VR Training for Fans: Mini-Games and Drills Clubs Could Offer After Meta’s Retreat
- Ship a Dining-App Style Microapp for Group Live Calls: A 7-Day Build Template
- Low-Sugar Viennese Fingers: Tweaks to Reduce Sweetness Without Losing Texture
- Monetizing Comic IP: Merch, Adaptations, and Revenue Split Models Explained
Related Topics
typescript
Contributor
Senior editor and content strategist. Writing about technology, design, and the future of digital media. Follow along for deep dives into the industry's moving parts.
Up Next
More stories handpicked for you
How to enable non-developers to ship micro apps using TypeScript templates
Migration guide: swapping an LLM provider in your TypeScript stack (e.g., to Gemini)
Geo-Personalization and TypeScript: Local Experience Cards & Client Contracts (2026)
From Our Network
Trending stories across our publication group