Theming and Layout in Astryx: Theme System, Frame-First Approach, and Templates
Master Astryx theming with 7 built-in themes, custom theme creation via defineTheme(), frame-first layout patterns, and page/block templates.
Published on • July 31, 2026
AI Assistant

Astryx’s theme system and layout approach work together to produce consistent, theme-agnostic UIs. The core idea: your app code never references specific colors or measurements. Switch themes without touching a single line of application logic.
The Theme System
Built-in Themes
Astryx ships with 7 themes. Each is a separate npm package:
| Theme | Package | Character |
|---|---|---|
| Matcha | @astryxdesign/theme-matcha | Earthy green, Figtree typography |
| Neutral | @astryxdesign/theme-neutral | Muted, minimal, system fonts |
| Butter | @astryxdesign/theme-butter | Golden surfaces, blue accents |
| Chocolate | @astryxdesign/theme-chocolate | Warm brown/beige, Fraunces typeface |
| Gothic | @astryxdesign/theme-gothic | Dark-only, deep blue-gray |
| Stone | @astryxdesign/theme-stone | Warm stone/slate, Montserrat + Figtree |
| Y2K | @astryxdesign/theme-y2k | Retro pop, periwinkle, holographic |
The Theme Component
Wrap your app (or a section) with <Theme> to activate a theme:
import { Theme } from '@astryxdesign/core/theme';
import { neutralTheme } from '@astryxdesign/theme-neutral/built';
function App() {
return (
<Theme theme={neutralTheme} mode="system">
<YourApp />
</Theme>
);
}
The mode prop accepts 'system' (follows OS preference), 'light', or 'dark'.
Nesting Themes
Different sections can use different themes:
<Theme theme={matchaTheme}>
<App>
<Theme theme={neutralTheme}>
<NeutralSection />
</Theme>
</Theme>
</Theme>
MediaTheme — Automatic Light/Dark Switching
Switch themes automatically based on prefers-color-scheme:
import { MediaTheme } from '@astryxdesign/core/MediaTheme';
<MediaTheme light="matcha" dark="gothic" />
No page refresh needed — it switches instantly.
Custom Themes with defineTheme()
Create fully custom themes with defineTheme(). You can configure colors, typography, radii, motion, and component overrides.
Simple Custom Theme
import { defineTheme } from '@astryxdesign/core/theme';
const myTheme = defineTheme({
name: 'custom',
tokens: {
'--color-accent': '#FF6B35',
'--color-background-surface': '#FEF9EF',
'--color-text-primary': '#1A1A2E',
},
});
Advanced Theme Configuration
const advancedTheme = defineTheme({
name: 'my-theme',
color: {
accent: '#7B61FF',
neutralStyle: 'cool',
},
typography: {
scale: { base: 14, ratio: 1.2 },
body: { family: 'Inter', fallbacks: '-apple-system, sans-serif' },
heading: { family: 'Inter', weight: 'bold' },
code: { family: 'JetBrains Mono' },
},
radius: { base: 4, multiplier: 1 },
motion: { fast: 175, medium: 410, ratio: 0.75 },
components: {
button: {
base: { borderRadius: '9999px', textTransform: 'uppercase' },
},
card: {
base: { borderRadius: '20px', padding: '24px' },
},
},
});
Extending a Built-in Theme
import { defineTheme } from '@astryxdesign/core/theme';
import { neutralTheme } from '@astryxdesign/theme-neutral';
const brandTheme = defineTheme({
name: 'brand',
extends: neutralTheme,
tokens: {
'--color-accent': ['#7B61FF', '#9B85FF'],
},
});
Building Themes for Production
npx astryx theme build ./src/themes/ocean.ts
Generates: ocean.css, ocean.js, ocean.d.ts, ocean.variants.d.ts.
useTheme Hook
Access current theme tokens and mode in JavaScript:
import { useTheme } from '@astryxdesign/core/theme';
function ChartConfig() {
const { mode, tokens } = useTheme();
const options = useMemo(() => ({
textColor: tokens['--color-text-primary'],
gridColor: tokens['--color-border'],
}), [mode, tokens]);
return <Chart options={options} />;
}
Frame-First Layout Approach
The fundamental rule: decide the frame before writing any content.
The 4-Step Process
- Pick the shell —
AppShell(top/side nav),Layout+LayoutPanel(multi-pane), or plain content column - Budget regions in px — side nav 240-280, icon rail 64-72, inspector 340-420, filter/facet rail 220-260
- Decide container policy — dense data = rows, widgets = cards
- Write the responsive contract — which regions collapse/overlay/drop at which breakpoints
Three-Region Tool Frame
<AppShell
sideNav={<SideNav>{/* items */}</SideNav>}
contentPadding={0}
>
<Layout>
<LayoutContent>{/* dense list or table, edge-to-edge */}</LayoutContent>
<LayoutPanel
width={380}
resizable={{ minSizePx: 320, maxSizePx: 480, autoSaveId: 'inspector' }}
hasDivider isScrollable label="Details"
>
{selected ? <DetailFields item={selected} /> : <EmptyState title="Nothing selected" />}
</LayoutPanel>
</Layout>
</AppShell>
App Archetypes
| Archetype | Frame | Container Policy |
|---|---|---|
| Tracker / work tool | AppShell + SideNav; inspector panel on select | Rows only. Zero cards. |
| Console / observability | AppShell + SideNav or TopNav + TabList | Card grid for widgets; Table for everything else |
| Messaging / feed | Rail + sidebar + stream + panel | Rows and bubbles. No cards in the stream. |
| Media library / gallery | AppShell + TopNav; grid content | Card grid with ClickableCard |
| Settings / forms | AppShell + SideNav or settings template | Sections with FormLayout; Card only for dangerous sections |
Cards vs Rows (Anti-Pattern: “Card Soup”)
| Use | For |
|---|---|
| Table (with plugins) | Columnar records: hosts, deployments, users |
| List / Item (edge-to-edge) | Single-line records: issues, files, conversations |
| Card | Self-contained widgets: KPI tiles, chart panels, gallery entries |
Templates
Astryx provides page and block templates to jumpstart development.
Page Templates
- AppShellTopNavWithSideNav — Full app shell with top nav + side nav + resizable content area
- SettingsPage — Settings with sections, side nav for category selection
- DashboardPage — Grid of cards for KPI/widget display
CLI Commands
npx astryx template --list # list all templates
npx astryx template --list --type page # filter page templates
npx astryx template DashboardPage --skeleton # study structure only
npx astryx template DashboardPage ./src/app # scaffold into project