Back to flin
flin

Tokens de diseño y sistema de temas

Cómo el sistema de tokens de diseño de FlinUI proporciona más de 50 tokens para colores, espaciado, tipografía y sombras -- habilitando temas consistentes y modo oscuro en más de 365 componentes.

Thales & Claude | March 30, 2026 9 min flin
EN/ FR/ ES
flinrust

When six AI agents build 70 components simultaneously, how do you ensure they all look like they belong to the same design system? The answer is design tokens -- a centralized set of values for colors, spacing, typography, shadows, and border radii that every component references instead of hardcoding.

FlinUI's design token system was the first thing we built in Session 037, before a single component was written. Fifty-plus tokens defined the visual language. Every Button, every Card, every Modal draws from the same well. Change a token, and every component that references it updates. Switch from light to dark theme, and the entire application transforms in a single state change.

Qué son los tokens de diseño

Design tokens are named constants that represent visual design decisions. Instead of scattering #007bff throughout 365 component files, you define it once as primary and reference it everywhere:

flin// tokens.flin
primary = "#007bff"
primary_hover = "#0056b3"
primary_light = "#e3f2fd"

// Button.flin uses the token, not the hex value
<button style="background: {primary}">

If the brand color changes from blue to purple, you change one line in tokens.flin. Every button, every link, every badge updates automatically.

This is not a new concept -- Salesforce Lightning Design System, Material Design, and Tailwind CSS all use design tokens. What is different in FLIN is that tokens are FLIN variables. They are not CSS custom properties, not JSON files, not JavaScript objects. They are reactive values that the component system understands natively.

Las categorías de tokens

Tokens de color

flin// Brand colors
primary = "#007bff"
primary_hover = "#0056b3"
primary_active = "#004085"
primary_light = "#e3f2fd"

secondary = "#6c757d"
secondary_hover = "#545b62"

// Semantic colors
success = "#28a745"
success_hover = "#218838"
warning = "#ffc107"
warning_hover = "#e0a800"
danger = "#dc3545"
danger_hover = "#c82333"
info = "#17a2b8"
info_hover = "#138496"

// Neutral colors
white = "#ffffff"
gray_50 = "#f8f9fa"
gray_100 = "#f1f3f5"
gray_200 = "#e9ecef"
gray_300 = "#dee2e6"
gray_400 = "#ced4da"
gray_500 = "#adb5bd"
gray_600 = "#6c757d"
gray_700 = "#495057"
gray_800 = "#343a40"
gray_900 = "#212529"
black = "#000000"

// Semantic surface colors
bg_primary = "#ffffff"
bg_secondary = "#f8f9fa"
bg_surface = "#ffffff"
text_primary = "#212529"
text_secondary = "#6c757d"
text_muted = "#adb5bd"
border_color = "#dee2e6"

The color system follows a deliberate hierarchy. Brand colors (primary, secondary) define the application's identity. Semantic colors (success, warning, danger, info) communicate meaning. Neutral colors (gray_50 through gray_900) provide the grayscale backbone. Surface colors (bg_primary, text_primary, border_color) define the base layer that changes between themes.

Each brand and semantic color has hover and active variants, computed as darker shades. This ensures consistent interaction feedback across all components without each component computing its own hover color.

Tokens de espaciado

flinspace_0 = "0"
space_px = "1px"
space_0_5 = "0.125rem"    // 2px
space_1 = "0.25rem"       // 4px
space_1_5 = "0.375rem"    // 6px
space_2 = "0.5rem"        // 8px
space_3 = "0.75rem"       // 12px
space_4 = "1rem"          // 16px
space_5 = "1.25rem"       // 20px
space_6 = "1.5rem"        // 24px
space_8 = "2rem"          // 32px
space_10 = "2.5rem"       // 40px
space_12 = "3rem"         // 48px
space_16 = "4rem"         // 64px
space_20 = "5rem"         // 80px
space_24 = "6rem"         // 96px

The spacing scale follows a 4px base grid. space_1 is 4px, space_2 is 8px, space_4 is 16px. The half-steps (space_0_5, space_1_5) exist for fine-tuning where the base scale is too coarse.

Components use spacing tokens for padding, margins, and gaps:

flin// Button.flin
padding_sm = "{space_1} {space_2}"      // 4px 8px
padding_md = "{space_2} {space_4}"      // 8px 16px
padding_lg = "{space_3} {space_6}"      // 12px 24px

Tokens de tipografía

flin// Font families
font_sans = "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif"
font_mono = "'SF Mono', 'Fira Code', 'Consolas', monospace"
font_serif = "Georgia, 'Times New Roman', serif"

// Font sizes
text_xs = "0.75rem"        // 12px
text_sm = "0.875rem"       // 14px
text_base = "1rem"         // 16px
text_lg = "1.125rem"       // 18px
text_xl = "1.25rem"        // 20px
text_2xl = "1.5rem"        // 24px
text_3xl = "1.875rem"      // 30px
text_4xl = "2.25rem"       // 36px

// Font weights
font_light = "300"
font_normal = "400"
font_medium = "500"
font_semibold = "600"
font_bold = "700"

// Line heights
leading_tight = "1.25"
leading_normal = "1.5"
leading_relaxed = "1.75"

// Letter spacing
tracking_tight = "-0.025em"
tracking_normal = "0"
tracking_wide = "0.025em"

The type scale uses a modular ratio of approximately 1.125 (major second). Each step is 1.125 times the previous step, creating a harmonious visual hierarchy. The text_base of 16px is the browser default, ensuring readability.

Tokens de sombra

flinshadow_xs = "0 1px 2px rgba(0,0,0,0.05)"
shadow_sm = "0 1px 3px rgba(0,0,0,0.1), 0 1px 2px rgba(0,0,0,0.06)"
shadow_md = "0 4px 6px rgba(0,0,0,0.1), 0 2px 4px rgba(0,0,0,0.06)"
shadow_lg = "0 10px 15px rgba(0,0,0,0.1), 0 4px 6px rgba(0,0,0,0.05)"
shadow_xl = "0 20px 25px rgba(0,0,0,0.1), 0 10px 10px rgba(0,0,0,0.04)"
shadow_inner = "inset 0 2px 4px rgba(0,0,0,0.06)"
shadow_none = "none"

Shadows create depth hierarchy. Cards use shadow_sm. Modals use shadow_lg. Dropdowns use shadow_md. The progression from xs to xl creates a consistent elevation system.

Tokens de radio de borde

flinradius_none = "0"
radius_sm = "0.125rem"     // 2px
radius_md = "0.25rem"      // 4px
radius_lg = "0.5rem"       // 8px
radius_xl = "0.75rem"      // 12px
radius_2xl = "1rem"        // 16px
radius_full = "9999px"     // Fully rounded (pill shape)

Tokens de transición

flintransition_fast = "150ms ease-in-out"
transition_normal = "250ms ease-in-out"
transition_slow = "350ms ease-in-out"

Components use these for hover effects, focus states, and theme transitions.

Modo oscuro: sobrecargas de tokens

Dark mode is not a CSS trick. It is a complete set of token overrides that replace the light theme values:

flin// dark.flin
bg_primary = "#1a1a2e"
bg_secondary = "#16213e"
bg_surface = "#0f3460"
text_primary = "#e2e8f0"
text_secondary = "#a0aec0"
text_muted = "#718096"
border_color = "#2d3748"

// Adjusted shadows for dark backgrounds
shadow_sm = "0 1px 3px rgba(0,0,0,0.3), 0 1px 2px rgba(0,0,0,0.2)"
shadow_md = "0 4px 6px rgba(0,0,0,0.3), 0 2px 4px rgba(0,0,0,0.2)"
shadow_lg = "0 10px 15px rgba(0,0,0,0.3), 0 4px 6px rgba(0,0,0,0.2)"

// Brand colors remain the same but with adjusted variants
primary_light = "#1a365d"
success_light = "#1c4532"
danger_light = "#4a1d2f"

When the theme switches from light to dark, the token values change, and every component that references them updates. A Card that uses bg_surface for its background automatically changes from white to dark blue. A Text component that uses text_primary automatically changes from near-black to near-white.

Detección de tema

FlinUI supports three theme modes: light, dark, and system (auto-detect):

flin// ThemeProvider.flin
theme = props.theme || "system"

// System detection
{if theme == "system"}
    // Check OS preference
    preferred = system_color_scheme()  // "light" or "dark"
    active_theme = preferred
{else}
    active_theme = theme
{/if}

// Apply tokens
{if active_theme == "dark"}
    // Override with dark tokens
{/if}

The system_color_scheme() function checks the operating system's preferred color scheme (via the prefers-color-scheme media query on the web). When the user changes their OS theme, the application updates automatically.

Temas personalizados

FlinUI's token system is designed for customization. A developer can override any token to create a branded theme:

flin// my-theme.flin
primary = "#ff6b35"          // Orange brand color
primary_hover = "#e55a2b"
primary_light = "#fff3ed"
radius_md = "0.5rem"         // Rounder corners
font_sans = "'Inter', sans-serif"  // Custom font

The custom theme file is loaded after the default tokens, overriding only the values that differ. Everything else -- spacing, shadows, typography scale -- remains consistent with the default theme.

flin<ThemeProvider theme={custom_theme}>
    <App />
</ThemeProvider>

The ThemeProvider component wraps the application and makes the custom token values available to all descendant components. Multiple ThemeProviders can nest for section-specific theming (a dark sidebar in a light application, for example).

Cómo fluyen los tokens a través de los componentes

The token system works through a combination of FLIN's variable scoping and the component rendering pipeline:

1. tokens.flin defines default values
2. dark.flin conditionally overrides values
3. ThemeProvider makes tokens available to child components
4. Components read tokens from their rendering context
5. Changes to tokens trigger re-renders in referencing components

This is not CSS custom properties (which require browser support and cascade rules). It is FLIN's own reactivity system applying to visual values. When primary changes from "#007bff" to "#ff6b35", every component that uses primary in its style attributes re-renders with the new value. The update is granular -- only the components that reference the changed token re-render.

Tokens responsivos

The responsive system defines breakpoints and provides utilities for responsive design:

flin// responsive.flin
breakpoint_sm = "640px"
breakpoint_md = "768px"
breakpoint_lg = "1024px"
breakpoint_xl = "1280px"
breakpoint_2xl = "1536px"

// Container max-widths
container_sm = "640px"
container_md = "768px"
container_lg = "1024px"
container_xl = "1280px"

Components use these breakpoints for responsive behavior. The Grid component, for example, can change its column count at different breakpoints:

flin<Grid cols={1} cols_md={2} cols_lg={3} gap={4}>
    <Card>...</Card>
    <Card>...</Card>
    <Card>...</Card>
</Grid>

On mobile (below breakpoint_md): one column. On tablets: two columns. On desktop: three columns.

Tokens de animación

flin// animations.flin
fade_in = "fadeIn 200ms ease-in-out"
fade_out = "fadeOut 200ms ease-in-out"
slide_up = "slideUp 250ms ease-out"
slide_down = "slideDown 250ms ease-out"
scale_in = "scaleIn 200ms ease-out"
scale_out = "scaleOut 150ms ease-in"
spin = "spin 1s linear infinite"
pulse = "pulse 2s ease-in-out infinite"
bounce = "bounce 1s ease-in-out"

Components reference these tokens for entry and exit animations. Modal uses fade_in for its overlay and scale_in for its content panel. Toast uses slide_up for appearing and fade_out for dismissing. Spinner uses spin for its rotation.

El resultado: consistente por construcción

The design token system means FlinUI is consistent not because each component was carefully reviewed for visual consistency, but because consistency is built into the architecture. A component cannot use a non-standard blue because there is no reason to -- primary is easier to type than #007bff. A component cannot use 15px padding because space_4 (16px) and space_3 (12px) are the available options.

Fifty-plus tokens. Two theme variants (light and dark). Six breakpoints. Nine animations. Together, they define a complete visual language that scales from a single button to a 365-component library without a single visual inconsistency.


Esta es la Parte 85 de la serie "Cómo construimos FLIN", que documenta cómo un CEO en Abiyán y un CTO de IA construyeron un sistema de tokens de diseño que mantiene 365 componentes visualmente consistentes.

Navegación de la serie: - [84] Componentes de gráficos y visualización de datos - [85] Tokens de diseño y sistema de temas (estás aquí) - [86] El sistema de diseño de página - [87] Integración de biblioteca de íconos

Share this article:

Responses

Write a response
0/2000
Loading responses...

Related Articles