Mastering Astryx / lesson 5 of 13

Theme System & Design Tokens

Master the core of Astryx Design System by implementing dynamic design tokens like spacing, shadows, and color modes. This tutorial shows you how to build a seamless theme toggle that respects user preferences.

Play
Transcript

[00:00] So there are a whole bunch of design tokens available to us in the Astryx design system, and they fall into a few categories. The first one being spacing. So it is, let me double check. It is based on a four pixel system. So spacing one is going to be four pixels. Spacing two is going to be eight pixels. Spacing four is going to be 16 pixels. The other one you get is shadows. So there’s three of these are shadow low, medium and high, and that’s going to tweak how the shadow on an element looks. So right now, it’s a little hard to see here, but this is a shadow medium when I hover over these guys. And these are the cards.

[00:48] Everything has every Astryx theme has a light and a dark schema. And what I’m doing right here is I’m making sure that I’m matching the system. If it’s set to system, then we want to get whatever this user system prefers. Outside of that, it’s mostly colors. So we’ve got color background muted, yada, yada, yada. And then there’s also radius container, which is going to be, you know, border radius on a container. Yeah. So what we’re going to do in this lesson is we’re going to implement a toggle, very similar to our language toggles. So we built this guy previously. We’re going to set one there for toggling through system light and dark.

[01:29] So we’re going to jump over here to our Kanban component. And right here after locale and set locale, we’re going to drop in mode, set mode, locale and set locale from use preferences, which we created previously in our providers. Just to remind you on that here is our use preferences function. And it’s managing these app preferences, which have mode, set mode, locale and set locale. So we’re setting all those here. So let’s find our language switch button. So here’s that button there and below this, we’re going to drop a new button. So we’ve got a label and we’re just cycling through. If the mode is system or light mode or light show dark mode and so on a variant of secondary.

[02:17] So again, one of those keys based on the, that controls the color of the text. And then we’ve got an on click that says, you know, which one to cycle to next. So it’s just a bunch of messy ternaries here, but nothing too complex. So we’ll save that guy. And I broke something. You know what? I have this twice. We don’t need that one. So we are looking at a dark mode right now or a system. I’m not quite sure. I’m going to hit light mode and everything’s light. And now you can really see those shadows pop. We’ll go to dark mode and when I hit system mode, of course, it’s going to be the same thing. So we can cycle through these guys. It took very little code, very little effort. It’s all built in. It’s automatic. It’s a very cool system that they have with this Astryx design system.

Astryx utilizes a comprehensive CSS custom properties (tokens) engine for colors, elevation, radii, and spatial gaps. In this lesson, we break down the token categories, map them onto the board styles we wrote in lesson 4, and add a light/dark toggle button to the board header.

Files this lesson: src/app/components/KanbanBoard.tsx (rewritten). page.tsx stays untouched.

Token Categories

1. Spacing Scale

Astryx provides a 4px-based spacing scale via CSS variables:

The board uses these everywhere: padding: 'var(--spacing-4)' on the column strip, gap={3} between stacked content, padding={3} on cards.

2. Shadows & Elevation

Elevation shadows communicate hierarchy and interactive states:

Card also exposes an elevation prop ('none' | 'low' | 'med' | 'high') that maps to these same tokens — the idiomatic shortcut. We use xstyle with the raw --shadow-* variables to teach the override mechanism, but whenever a built-in prop exists, prefer it.

3. Container Radii

4. Background Colors

Light & Dark: It’s All One Theme

Astryx themes carry both a light and a dark token set — color tokens are [light, dark] tuples, and the <Theme> provider (which you installed in lesson 1) resolves whichever mode is active. mode="system" follows the OS, which is exactly why the @media (prefers-color-scheme: dark) hover conditionals from lesson 4 already activate on dark-mode machines: no code required.

Forcing a mode lets you test both sets without changing your OS setting. <Theme> accepts mode: 'system' | 'light' | 'dark' and manages data-theme / color-scheme on the DOM, which prefers-color-scheme follows — so your custom StyleX conditionals respond to the toggle with zero extra code.

Adding the Toggle to the Board

usePreferences() from lesson 3 already exposes mode and setMode. Wire a toggle button into the board header that cycles system → light → dark → system:

src/app/components/KanbanBoard.tsx
'use client';
import { Section } from '@astryxdesign/core/Section';
import {
Layout,
LayoutHeader,
LayoutContent,
HStack,
VStack,
} from '@astryxdesign/core/Layout';
import { Heading, Text } from '@astryxdesign/core/Text';
import { Card } from '@astryxdesign/core/Card';
import { Button } from '@astryxdesign/core/Button';
import { useTranslator } from '@astryxdesign/core/i18n';
import { usePreferences } from '../providers';
import { styles } from '../styles';
export default function KanbanBoard() {
const t = useTranslator();
const { locale, setLocale } = usePreferences();
const { mode, setMode, locale, setLocale } = usePreferences();
return (
<Section height="100dvh">
<Layout
height="fill"
header={
<LayoutHeader hasDivider padding={4}>
<HStack hAlign="between" vAlign="center">
<Heading level={3}>{t('@app.board.title')}</Heading>
<HStack gap={2} vAlign="center">
<Text type="supporting" color="secondary" hasTabularNumbers>
4 {t('@app.tasks')}
</Text>
{/* Language toggle (lesson 3) */}
<Button
label={t('@app.switchLanguage')}
variant="secondary"
onClick={() => setLocale(locale === 'en' ? 'fr' : 'en')}
/>
{/* Theme toggle: system → light → dark → system */}
<Button
label={
mode === 'system'
? 'Light mode'
: mode === 'light'
? 'Dark mode'
: 'System mode'
}
variant="secondary"
onClick={() =>
setMode(
mode === 'system'
? 'light'
: mode === 'light'
? 'dark'
: 'system',
)
}
/>
</HStack>
</HStack>
</LayoutHeader>
}
content={
<LayoutContent padding={0}>
<HStack gap={4} xstyle={styles.boardColumns}>
{[0, 1, 2, 3].map(i => (
<Card
key={i}
variant="muted"
padding={0}
xstyle={styles.columnShell}
>
<VStack gap={3} padding={3}>
<Heading level={4}>Column {i + 1}</Heading>
<Card padding={3} xstyle={styles.card}>
<Heading level={5}>Placeholder task</Heading>
<Text type="supporting" color="secondary" maxLines={2}>
A placeholder card inside the styled column shell.
</Text>
</Card>
</VStack>
</Card>
))}
</HStack>
</LayoutContent>
}
/>
</Section>
);
}

The button label always shows the next mode: from system it offers Light, from Light it offers Dark, from Dark it returns to System. Watch the whole board — column surfaces, text, borders, and the hover ring — re-theme instantly.

Semantic Variants

Astryx components like StatusDot and Badge accept semantic variant props. These map domain states to theme colors, and they are the vocabulary our board’s column/priority model uses from lesson 6 onward:

VariantPurposeKanban Board Use Case
neutralBaseline / default stateTo-do column, Task reference ID
accentIn-progress / primary actionIn-progress column status
warningNeeds attention / reviewIn-review column status, Medium priority
successCompleted / resolved stateDone column status
errorHigh urgency / blockerHigh priority tasks
tealLow urgency / infoLow priority tasks

Variant vocabularies overlap but differ: accent is a StatusDot variant. The accent-colored badge variant is named info (it uses the same --color-accent token), so a badge equivalent of the “In progress” dot is <Badge variant="info" />.

You’ll see these in action starting in lesson 6, when the board renders real data with priority badges.

See It in Action

Run npm run dev. Click the theme toggle: Light mode forces light even if your OS is dark; Dark mode forces dark on a light OS; System mode hands control back to the OS. Flip to dark and hover a task card — the light ring + deep shadow from styles.card appears, and the language toggle still works independently. Each setting is plain React state in providers.tsx; nothing about the components changes.

What You Built

In the next lesson, we will model the board’s data and render real columns and tasks.


Share this post on:

Previous
Styling Components & StyleX Integration
Next
Data Modeling with Astryx Icons & Semantic Variants