Documentation
This page is built with @astralui/core - every preview below is the real, shipped component. Use the toggle (top-right) to flip light/dark and watch it all respond.
▶ Open the live playground - the real React components, loaded from npm, fully interactive.
Install & setup
Add the package and its peers, import the stylesheet once, then wrap your app.
pnpm add @astralui/core react react-dom @tabler/icons-reactimport '@astralui/core/styles.css';
import { ColorSchemeProvider, AstralThemeProvider, AstralToaster } from '@astralui/core';
function Root() {
return (
<ColorSchemeProvider defaultScheme="dark">
<AstralThemeProvider>
<AstralToaster />
<App />
</AstralThemeProvider>
</ColorSchemeProvider>
);
}Color scheme
The provider persists the scheme and sets data-astral-scheme + color-scheme on <html>. The button below calls the same logic the hook exposes.
const { colorScheme, toggle } = useColorScheme();
<button onClick={toggle}>{colorScheme}</button>
Per-brand theming
Pass a brand color and AstralUI builds a 10-shade palette via generateColors(). Try recoloring this whole page:
<AstralThemeProvider colors={{ primary_color: '#0d9488' }}>
<App />
</AstralThemeProvider>
// live, unsaved preview (e.g. a brand-color editor):
const { setPreview } = useThemePreview();
setPreview({ primary_color: '#e8590c' });
// buildOrgCss(colors) returns the raw <style> text if you need to inject it yourself (SSR)
Beyond the brand palette, the token layer exposes semantic vars you can use in
your own CSS: --au-text, --au-dim, --au-line,
--au-surface-2, --au-brand, --au-green /
--au-amber / --au-red / --au-blue, and
--au-accent (a warm-orange secondary accent), plus the raw
--astral-color-* palette. All are theme-aware via light-dark().
Buttons
<button className="au-btn primary">Primary</button>
<button className="au-btn danger">Delete</button>
Inputs
<div className="au-field-label">Email</div>
<div className="au-input"><input /></div>
<textarea className="au-textarea" />
Select
Single-select combobox (searchable / clearable / creatable), portalled above modals.
<AstralSelect value={v} onChange={setV} searchable clearable
options={[{ value: 'us', label: 'United States' }]} />
Menu
<AstralMenu items={[
{ label: 'Edit', onClick: edit },
{ label: 'Delete', danger: true, onClick: remove },
]} />
Modal & Drawer
<AstralModal opened={open} onClose={close} title="Edit profile" width={460}>
...
</AstralModal>
// AstralDrawer: same props, slides in from the right
<AstralDrawer opened={open} onClose={close} title="Filters" width={440}>
...
</AstralDrawer>
ConfirmModal
<ConfirmModal opened={open} onClose={close} onConfirm={remove}
title="Delete campaign?" message="This cannot be undone."
confirmLabel="Delete" danger />
PinInput
<AstralPinInput length={6} value={code} onChange={setCode} />
Notifications
notifications.show({ message: 'Saved', color: 'green' });
const id = notifications.show({ message: 'Working...', loading: true, autoClose: false });
notifications.update({ id, message: 'Done', color: 'green', loading: false, autoClose: 2000 });
DateInput
<DateInput type="date" value={date} onChange={setDate} />
// helpers: Date -> input string (local), e.g. for min/max or defaults
dateToInputStr(date); // "2026-06-06"
dateTimeToInputStr(date); // "2026-06-06T14:30"
StatusBadge
<StatusBadge status="running" label="Running" />
<StatusBadge status="custom" label="Paused" color="#eab308" />
Avatar
<Avatar name="Ada Byron" url={pictureUrl} size={44} />
<Avatar name="Jane Doe" radius={14} /> // initials fallback
SearchInput
A clearable search field. The demo below filters the list live as you type - in real use you'd debounce the value in the parent before querying an API.
const [q, setQ] = useState('');
<SearchInput value={q} onChange={setQ} placeholder="Search components..." />
// debounce in the parent when querying an API:
useEffect(() => {
const t = setTimeout(() => runQuery(q), 300);
return () => clearTimeout(t);
}, [q]);
Collapsible
A folding panel whose content animates in and out - the region transitions an exact measured max-height (grid 0fr/1fr snaps shut on collapse in some browsers) and the chevron rotates. Uncontrolled via defaultOpen, or controlled via open / onOpenChange.
const [open, setOpen] = useState(false);
// uncontrolled
<Collapsible title="Connection funnel">
<FunnelBars data={funnel} />
</Collapsible>
// controlled
<Collapsible open={open} onOpenChange={setOpen} title="Details">…</Collapsible>
Spinner
<Spinner size={24} />
Sparkline & Delta
<Sparkline points={[3,5,4,8,7,11]} color="violet.6" />
<Delta current={420} previous={377} />
// chartColor() resolves "hue.shade" -> var(--astral-color-...); hex/var pass through
chartColor('violet.6'); // "var(--astral-color-violet-6)"
Donut, Funnel & Stacked bars
Hover a slice, bar, or legend item - the highlight is native to the component (linked hover, others dim).
<Donut centerValue="1,284" centerLabel="sent" data={[
{ name: 'Accepted', value: 412, color: 'green.6' },
{ name: 'Pending', value: 872, color: 'violet.6' },
]} />
<FunnelBars data={[{ stage: 'Sent', value: 1284, color: 'violet.6' }]} />
<StackedBars series={[...]} rows={[...]} />
Auth shell
Centered sign-in / sign-up layout. Build the form with the au-auth-* classes.
<AuthShell backdrop="space">
<AuthCard banner={<Logo />}>
<h1 className="au-auth-title">Welcome back</h1>
<form className="au-auth-form">
<div className="au-auth-field"><label>Email</label>
<div className="au-auth-input"><input /></div></div>
<button className="au-auth-btn primary block">Sign in</button>
</form>
</AuthCard>
</AuthShell>
Backgrounds
<SpaceBackground fixed />
<GridBackground>...</GridBackground>