Styling Library Interop
Integrate Tailwind, StyleX, Panda, Chakra, CSS-in-JS, CSS Modules, and non-CSS renderers with system tokens.Core Principle #
Keep the system as the source of truth for theme values. Components read design tokens from CSS custom properties such as --color-text-primary, --color-background-surface, --spacing-4, and --radius-container. Other styling libraries should map their own semantic tokens, utility names, or theme objects to those system CSS variables whenever possible.
Use CSS variables for ordinary DOM styling because they inherit through the tree, follow data-theme color mode, respect nested data-astryx-theme scopes, and update when themes switch. Use token resolver APIs only for non-CSS consumers such as SVG attribute values, canvas, chart configuration, color calculations, or static config generation.
For available token names and values, run astryx-svelte docs tokens. Focused references are also available with astryx-svelte docs color, astryx-svelte docs spacing, astryx-svelte docs shape, astryx-svelte docs typography, astryx-svelte docs elevation, and astryx-svelte docs motion.
Choose an Integration Path #
Choose the narrowest integration path that fits the styling library. Most DOM styling should stay on the CSS-variable path; JavaScript token resolution is for APIs that cannot consume CSS custom properties.
| Path | Use when | Value shape |
|---|---|---|
| CSS variable aliases | The library ultimately writes CSS and accepts string values | var(--color-text-primary) |
| StyleX | You are writing StyleX styles in a .stylex.ts module | { color: 'var(--color-text-primary)' } |
| Tailwind @theme inline | You want utility classes backed by active system tokens | --color-surface: var(--color-background-surface) |
| Token resolver APIs | JavaScript needs token values for charts, canvas, SVG, or config objects | resolveThemeToken(theme, '--color-icon-blue', {mode}) |
Best Practices #
| Guidance | Practices |
|---|---|
| Do | Map by semantic intent: text, surface, border, accent, status, radius, spacing, typography. |
| Do | Let the system own color mode. The root Theme syncs data-theme="light|dark" and data-astryx-theme to <html> for portals and first-level theme scope. |
| Do | Prefer CSS variables for runtime theme switching and nested themes. |
| Don't | Copy raw hex/px values into a second theme object when a var(...) reference would work. |
| Don't | Run a second unsynchronized dark-mode provider that disagrees with Theme. |
| Don't | Make another library's CSS variables the source of truth for the system. Some consumers need token values outside the DOM. |
Plain CSS and CSS Modules #
The simplest integration is direct CSS variable usage. CSS Modules scope class names, but system token variables are global/inherited values supplied by package CSS and the active theme. The same is true of a Svelte <style> block: the scoping hash applies to the selector, not to the custom properties it reads.
css.card {background: var(--color-background-surface);color: var(--color-text-primary);border: 1px solid var(--color-border);border-radius: var(--radius-container);padding: var(--spacing-4);}
Sass variables are compile-time only. They are useful for generating static CSS, but they do not update when the system switches theme or color mode. Use native CSS custom properties for themeable values.
StyleX #
For StyleX styles, reference tokens as var(--token) strings. The token names are the published surface; the defineVars objects that mint them are internal to @astryx-svelte/core and are not exported from any subpath, so there are no typed token imports to reach for.
Author every stylex.create call in a .ts module. StyleX may not be imported from a .svelte file — the plugin Babel-parses anything that imports it and would read the markup as JSX.
tsimport * as stylex from '@stylexjs/stylex';export const styles = stylex.create({panel: {backgroundColor: 'var(--color-background-surface)',color: 'var(--color-text-primary)',padding: 'var(--spacing-4)',borderRadius: 'var(--radius-container)'}});
Use xstyle for component overrides and stylex.props() (through the sx adapter) for your own DOM nodes. Use class when integrating a non-StyleX styling library.
Tailwind #
Tailwind v4 reads its theme from CSS custom properties, so no plugin is needed: map Tailwind's theme variables to the system's with @theme inline and utility classes like text-primary, bg-surface, border-border, rounded-lg, and shadow-md stay in sync with the active theme.
css@layer reset, theme, base, astryx-base, astryx-theme, product, utilities;@import 'tailwindcss/theme.css' layer(theme);@import 'tailwindcss/preflight.css' layer(base);@import '@astryx-svelte/core/base.css';@import '@astryx-svelte/theme-neutral/theme.css';@import 'tailwindcss/utilities.css' layer(utilities);@theme inline {--color-surface: var(--color-background-surface);--color-primary: var(--color-text-primary);--color-border: var(--color-border);--radius-lg: var(--radius-container);--shadow-md: var(--shadow-med);}
Pre-declare every layer before any imports. This keeps reset lowest, Tailwind preflight above reset, component/theme styles in the middle, and Tailwind utilities last so utility classes on class can intentionally override component defaults.
svelte<section class="rounded-lg border border-border bg-surface p-4 text-primary shadow-md"><Button label="Save" variant="primary" /></section>
Tailwind is the concrete example of the general interop pattern: expose another library's semantic API, but point the values at system token variables.
Panda, Chakra, and Other Semantic Token Systems #
Libraries with first-class semantic token objects, such as Panda CSS, let you put system CSS variables at the leaves so product code can use the library's semantic names while the system still owns the values.
tssemanticTokens: {colors: {text: {primary: { value: 'var(--color-text-primary)' },secondary: { value: 'var(--color-text-secondary)' }},background: {surface: { value: 'var(--color-background-surface)' },body: { value: 'var(--color-background-body)' }},border: {default: { value: 'var(--color-border)' }}}},tokens: {spacing: {4: { value: 'var(--spacing-4)' }},radii: {container: { value: 'var(--radius-container)' }}}
svelte<sectionclass={css({bg: 'background.surface',color: 'text.primary',borderColor: 'border.default',p: '4',rounded: 'container'})}></section>
If a semantic-token library needs to generate its own light/dark CSS from raw values, align its mode selector with Theme (data-theme="light|dark") and generate that adapter from system theme data. Otherwise it can drift from nested or runtime themes.
Emotion, Theme UI, and Other CSS-in-JS Theme Objects #
Runtime CSS-in-JS libraries usually accept arbitrary theme objects. Keep those objects semantic, but store system CSS variable references as the values. This keeps generated classes stable while the system updates values through the CSS cascade.
tsconst appTheme = {colors: {textPrimary: 'var(--color-text-primary)',textSecondary: 'var(--color-text-secondary)',surface: 'var(--color-background-surface)',border: 'var(--color-border)',accent: 'var(--color-accent)'},spacing: {4: 'var(--spacing-4)'},radius: {container: 'var(--radius-container)'}};
Avoid rebuilding CSS-in-JS theme objects with raw color values on every mode switch. CSS variables let the class names stay the same while the browser resolves the active values.
UnoCSS and Custom Utility Systems #
Utility generators such as UnoCSS can put system variables in their theme config or shortcuts. Keep classes semantic (bg-surface, text-primary) and let the values point at system tokens.
tsexport default defineConfig({theme: {colors: {surface: 'var(--color-background-surface)',primary: 'var(--color-text-primary)',border: 'var(--color-border)',accent: 'var(--color-accent)'},spacing: {4: 'var(--spacing-4)'}},shortcuts: {'astryx-card': 'bg-surface text-primary border border-border rounded-lg p-4'}});
Static utility extractors cannot see dynamically constructed class names. Prefer explicit class strings or the library's safelist/source-registration mechanism.
Non-CSS Processing #
Use resolveThemeTokens() or resolveThemeToken() when code outside a component needs token values for a known theme and mode. Use useTheme() inside a component when the values should come from the nearest Theme and active mode.
tsimport { resolveThemeTokens } from '@astryx-svelte/core/theme';import { neutralTheme } from '@astryx-svelte/theme-neutral/tokens';const tokens = resolveThemeTokens(neutralTheme, { mode: 'light' });const chartOptions = {textColor: tokens['--color-text-primary'],mutedTextColor: tokens['--color-text-secondary'],gridColor: tokens['--color-border'],seriesColors: [tokens['--color-icon-blue'],tokens['--color-icon-orange'],tokens['--color-icon-purple']]};
svelte<script lang="ts">import { useTheme } from '@astryx-svelte/core/theme';let { data }: { data: Array<{ x: string; y: number }> } = $props();const theme = useTheme();const chartOptions = $derived({mode: theme.mode,textColor: theme.tokens['--color-text-primary'],mutedTextColor: theme.tokens['--color-text-secondary'],gridColor: theme.tokens['--color-border'],seriesColors: [theme.tokens['--color-icon-blue'],theme.tokens['--color-icon-orange'],theme.tokens['--color-icon-purple']]});</script><ThirdPartyChart {data} options={chartOptions} />
Non-CSS Processing Best Practices #
| Guidance | Practices |
|---|---|
| Do | Read theme.tokens inside $derived; the return of useTheme() is getter-backed, so destructuring it once freezes the value. |
| Do | Use the distinct hue tokens (--color-icon-blue, --color-icon-orange, ...) for chart series instead of reusing arbitrary UI colors. |
| Do | Prefer CSS variables for SVG elements when possible (fill="var(--color-accent)"); use token resolver APIs when an API requires a string value in JavaScript. |
| Don't | Use token resolver APIs for ordinary DOM styling. Use CSS variables, StyleX, xstyle, or library aliases instead. |
| Don't | Assume the returned values reflect every CSS cascade override. They resolve tokens for the current theme and mode; local media-surface overrides and arbitrary CSS overrides may not be represented in the returned map. |
Interop Checklist #
- Import
@astryx-svelte/core/base.cssand a theme CSS file early enough for first paint. For a server-rendered app, build custom themes withastryx-svelte theme build; published themes are already built. - Choose one owner for color mode. Theme uses
data-theme="light|dark"andcolor-schemeto resolvelight-dark()tokens. - Map the external library's semantic layer to system variables by intent, not by exact naming. For example, a library's
background.papermaps to--color-background-surface. - Use
astryx-svelte docs tokensand focused token docs when building mappings. Keep mappings small at first: text, surface/body/card/popover, border, accent, status, spacing, radius, typography, shadow. - Use token resolver APIs only for non-CSS APIs that need resolved values.