Foundations/Tokens Architecture & Multi-Platform Pipeline

Tokens Architecture & Multi-Platform Pipeline

● Ready Web Nuxt 4 + Mobile React Native

Arsitektur tokenisasi berjenjang 3-Tier yang menjamin satu sumber kebenaran (Single Source of Truth) untuk Web (Nuxt 4 / Tailwind CSS v4) dan Mobile (React Native / StyleSheet).

1. Arsitektur 3-Tier

Tier 1: Global Tokens

Nilai mentah primitif tanpa konteks semantik spesifik (misal: blue-500: #2F6FEB, space-4: 16px, radius-md: 10px).

export const globalTokens = {
  colors: { blue500: '#2F6FEB', blue400: '#4C86F0', blue600: '#2557C9' },
  radii: { xs: 6, sm: 8, md: 10, lg: 14, xl: 18 },
  spacing: { 1: 4, 2: 8, 3: 12, 4: 16, 6: 24, 8: 32 }
}

Tier 2: Semantic / Alias Tokens

Token yang diberi makna fungsional untuk memudahkan konsistensi tema gelap/terang dan aksesibilitas.

export const semanticTokens = {
  color: {
    primary: globalTokens.colors.blue500,
    surface: '#FFFFFF',
    textMain: '#16161A',
    textMuted: '#6B7280'
  }
}

Tier 3: Component Tokens

Token spesifik yang terikat pada varian komponen tertentu (misal: button-primary-bg, card-tray-radius).

export const buttonTokens = {
  primary: {
    bg: semanticTokens.color.primary,
    radius: globalTokens.radii.xs, // 6px
    heightMd: 44
  }
}

2. Tabel Referensi Token

Cari nilai satu token untuk Web (CSS Variable/Tailwind) atau Mobile (React Native).

Nama TokenCSS Variable / TailwindNilai
surface--color-surface#ffffff
surface-alt--color-surface-alt#f7f8fa
surface-sunken--color-surface-sunken#f4f5f8
skeleton--color-skeleton#e8eaef
ink--color-ink#16161a
muted--color-muted#6b7280
faint--color-faint#9aa2b1
placeholder--color-placeholder#98a2b3
line--color-line#e5e7eb
hairline--color-hairlinergba(148, 163, 184, 0.18)
hairline-strong--color-hairline-strongrgba(148, 163, 184, 0.28)
primary--color-primary#2f6feb
primary-light--color-primary-light#4c86f0
primary-dark--color-primary-dark#2557c9
primary-soft--color-primary-soft#eaf1ff
star--color-star#f59e0b
success--color-success#22c55e
success-ink--color-success-ink#15803d
success-soft--color-success-soft#e8f8ee
danger--color-danger#c42132
danger-ink--color-danger-ink#7f151f
danger-soft--color-danger-soft#faebec
warning--color-warning#d8730e
warning-ink--color-warning-ink#a3570a
warning-soft--color-warning-soft#faf2ea
radius-xs--radius-xs6px
radius-sm--radius-sm8px
radius-md--radius-md10px
radius-lg--radius-lg14px
radius-xl--radius-xl18px
radius-pill--radius-pill999px

3. Ekspor Token Multi-Platform

Ketiga file di bawah ini bukan salinan manual — semuanya digenerate langsung dari tokens/tokens.json lewat npm run tokens:build, jadi tidak bisa lagi berbeda dari kode sungguhan.

React Native TypeScript Tokens

app/assets/tokens/generated/react-native.ts
// AUTO-GENERATED by scripts/build-tokens.mjs — do not edit by hand.
// Source of truth: tokens/tokens.json. Run `npm run tokens:build` after editing it.
// container.* is web-layout-only and intentionally omitted here.

export const tokens = {
  "color": {
    "surface": "#ffffff",
    "surfaceAlt": "#f7f8fa",
    "surfaceSunken": "#f4f5f8",
    "skeleton": "#e8eaef",
    "ink": "#16161a",
    "muted": "#6b7280",
    "faint": "#9aa2b1",
    "placeholder": "#98a2b3",
    "line": "#e5e7eb",
    "hairline": "rgba(148, 163, 184, 0.18)",
    "hairlineStrong": "rgba(148, 163, 184, 0.28)",
    "primary": "#2f6feb",
    "primaryLight": "#4c86f0",
    "primaryDark": "#2557c9",
    "primarySoft": "#eaf1ff",
    "star": "#f59e0b",
    "success": "#22c55e",
    "successInk": "#15803d",
    "successSoft": "#e8f8ee",
    "danger": "#c42132",
    "dangerInk": "#7f151f",
    "dangerSoft": "#faebec",
    "warning": "#d8730e",
    "warningInk": "#a3570a",
    "warningSoft": "#faf2ea"
  },
  "font": {
    "sans": "Plus Jakarta Sans",
    "serif": "Playfair Display",
    "mono": "JetBrains Mono"
  },
  "radius": {
    "xs": 6,
    "sm": 8,
    "md": 10,
    "lg": 14,
    "xl": 18,
    "pill": 999
  },
  "shadow": {
    "xs": {
      "shadowColor": "#0f172a",
      "shadowOffset": {
        "width": 0,
        "height": 1
      },
      "shadowOpacity": 0.04,
      "shadowRadius": 2,
      "elevation": 1
    },
    "lift": {
      "shadowColor": "#0f172a",
      "shadowOffset": {
        "width": 0,
        "height": 12
      },
      "shadowOpacity": 0.07,
      "shadowRadius": 26,
      "elevation": 9
    },
    "float": {
      "shadowColor": "#0f172a",
      "shadowOffset": {
        "width": 0,
        "height": 14
      },
      "shadowOpacity": 0.07,
      "shadowRadius": 34,
      "elevation": 11
    },
    "brand": {
      "shadowColor": "#2f6feb",
      "shadowOffset": {
        "width": 0,
        "height": 26
      },
      "shadowOpacity": 0.22,
      "shadowRadius": 54,
      "elevation": 18
    }
  },
  "ease": {
    "smooth": [
      0.22,
      1,
      0.36,
      1
    ]
  }
} as const;

Tailwind CSS v4 Theme Block (`@theme`)

app/assets/tokens/generated/theme.css
/* AUTO-GENERATED by scripts/build-tokens.mjs — do not edit by hand.
   Source of truth: tokens/tokens.json. Run `npm run tokens:build` after editing it. */

@theme {
  --color-surface: #ffffff;
  --color-surface-alt: #f7f8fa;
  --color-surface-sunken: #f4f5f8;
  --color-skeleton: #e8eaef;
  --color-ink: #16161a;
  --color-muted: #6b7280;
  --color-faint: #9aa2b1;
  --color-placeholder: #98a2b3;
  --color-line: #e5e7eb;
  --color-hairline: rgba(148, 163, 184, 0.18);
  --color-hairline-strong: rgba(148, 163, 184, 0.28);
  --color-primary: #2f6feb;
  --color-primary-light: #4c86f0;
  --color-primary-dark: #2557c9;
  --color-primary-soft: #eaf1ff;
  --color-star: #f59e0b;
  --color-success: #22c55e;
  --color-success-ink: #15803d;
  --color-success-soft: #e8f8ee;
  --color-danger: #c42132;
  --color-danger-ink: #7f151f;
  --color-danger-soft: #faebec;
  --color-warning: #d8730e;
  --color-warning-ink: #a3570a;
  --color-warning-soft: #faf2ea;
  --font-sans: 'Plus Jakarta Sans', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  --font-serif: 'Playfair Display', Georgia, serif;
  --font-mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
  --radius-xs: 6px;
  --radius-sm: 8px;
  --radius-md: 10px;
  --radius-lg: 14px;
  --radius-xl: 18px;
  --radius-pill: 999px;
  --shadow-xs: 0 1px 2px rgba(15, 23, 42, 0.04);
  --shadow-lift: 0 12px 26px rgba(15, 23, 42, 0.07);
  --shadow-float: 0 14px 34px rgba(15, 23, 42, 0.07);
  --shadow-brand: 0 26px 54px rgba(47, 111, 235, 0.22);
  --ease-smooth: cubic-bezier(0.22, 1, 0.36, 1);
  --container-page: 1720px;
}

Universal JSON Tokens (Figma / Design Tokens Community Group format)

app/assets/tokens/generated/tokens.json
{
  "color": {
    "surface": {
      "$type": "color",
      "$value": "#ffffff"
    },
    "surface-alt": {
      "$type": "color",
      "$value": "#f7f8fa"
    },
    "surface-sunken": {
      "$type": "color",
      "$value": "#f4f5f8"
    },
    "skeleton": {
      "$type": "color",
      "$value": "#e8eaef"
    },
    "ink": {
      "$type": "color",
      "$value": "#16161a"
    },
    "muted": {
      "$type": "color",
      "$value": "#6b7280"
    },
    "faint": {
      "$type": "color",
      "$value": "#9aa2b1",
      "$description": "2.57:1 on white — fails WCAG AA for text. Decorative/large-UI/non-text use only (e.g. large icons, chart strokes); never body/label/caption text — use color.muted for any real readable text instead."
    },
    "placeholder": {
      "$type": "color",
      "$value": "#98a2b3",
      "$description": "2.58:1 on white — fails WCAG AA for text. Restricted to the native ::placeholder pseudo-element on editable inputs; never for standalone visible UI copy — use color.muted there instead."
    },
    "line": {
      "$type": "color",
      "$value": "#e5e7eb"
    },
    "hairline": {
      "$type": "color",
      "$value": "rgba(148, 163, 184, 0.18)"
    },
    "hairline-strong": {
      "$type": "color",
      "$value": "rgba(148, 163, 184, 0.28)"
    },
    "primary": {
      "$type": "color",
      "$value": "#2f6feb"
    },
    "primary-light": {
      "$type": "color",
      "$value": "#4c86f0"
    },
    "primary-dark": {
      "$type": "color",
      "$value": "#2557c9"
    },
    "primary-soft": {
      "$type": "color",
      "$value": "#eaf1ff"
    },
    "star": {
      "$type": "color",
      "$value": "#f59e0b"
    },
    "success": {
      "$type": "color",
      "$value": "#22c55e"
    },
    "success-ink": {
      "$type": "color",
      "$value": "#15803d"
    },
    "success-soft": {
      "$type": "color",
      "$value": "#e8f8ee"
    },
    "danger": {
      "$type": "color",
      "$value": "#c42132",
      "$description": "Icon/border/dot/solid-button-background use only, paired with white text. Never as body text on white — use color.danger-ink for that."
    },
    "danger-ink": {
      "$type": "color",
      "$value": "#7f151f",
      "$description": "The only Danger tone approved for text-on-light-background use."
    },
    "danger-soft": {
      "$type": "color",
      "$value": "#faebec"
    },
    "warning": {
      "$type": "color",
      "$value": "#d8730e",
      "$description": "Icon/border/dot/solid-button-background use only. Never as body text on white — use color.warning-ink for that."
    },
    "warning-ink": {
      "$type": "color",
      "$value": "#a3570a",
      "$description": "The only Warning tone approved for text-on-light-background use."
    },
    "warning-soft": {
      "$type": "color",
      "$value": "#faf2ea"
    }
  },
  "font": {
    "sans": {
      "$type": "fontFamily",
      "$value": [
        "Plus Jakarta Sans",
        "system-ui",
        "-apple-system",
        "BlinkMacSystemFont",
        "Segoe UI",
        "Roboto",
        "sans-serif"
      ]
    },
    "serif": {
      "$type": "fontFamily",
      "$value": [
        "Playfair Display",
        "Georgia",
        "serif"
      ]
    },
    "mono": {
      "$type": "fontFamily",
      "$value": [
        "JetBrains Mono",
        "ui-monospace",
        "SFMono-Regular",
        "Menlo",
        "Monaco",
        "Consolas",
        "monospace"
      ]
    }
  },
  "radius": {
    "xs": {
      "$type": "dimension",
      "$value": "6px"
    },
    "sm": {
      "$type": "dimension",
      "$value": "8px"
    },
    "md": {
      "$type": "dimension",
      "$value": "10px"
    },
    "lg": {
      "$type": "dimension",
      "$value": "14px"
    },
    "xl": {
      "$type": "dimension",
      "$value": "18px"
    },
    "pill": {
      "$type": "dimension",
      "$value": "999px"
    }
  },
  "shadow": {
    "xs": {
      "$type": "shadow",
      "$value": {
        "offsetX": "0px",
        "offsetY": "1px",
        "blur": "2px",
        "spread": "0px",
        "color": "rgba(15, 23, 42, 0.04)"
      }
    },
    "lift": {
      "$type": "shadow",
      "$value": {
        "offsetX": "0px",
        "offsetY": "12px",
        "blur": "26px",
        "spread": "0px",
        "color": "rgba(15, 23, 42, 0.07)"
      }
    },
    "float": {
      "$type": "shadow",
      "$value": {
        "offsetX": "0px",
        "offsetY": "14px",
        "blur": "34px",
        "spread": "0px",
        "color": "rgba(15, 23, 42, 0.07)"
      }
    },
    "brand": {
      "$type": "shadow",
      "$value": {
        "offsetX": "0px",
        "offsetY": "26px",
        "blur": "54px",
        "spread": "0px",
        "color": "rgba(47, 111, 235, 0.22)"
      }
    }
  },
  "ease": {
    "smooth": {
      "$type": "cubicBezier",
      "$value": [
        0.22,
        1,
        0.36,
        1
      ]
    }
  },
  "container": {
    "page": {
      "$type": "dimension",
      "$value": "1720px",
      "$description": "Web layout only — omitted from the React Native export."
    }
  }
}