@var-ui/astro
v0.1.0
Published
Astro components for the var-ui design system
Downloads
494
Maintainers
Readme
@var-ui/astro
Astro components for the var-ui design system — same visual system as
@var-ui/react, no React runtime. Components ship as source .astro and
.ts files; your Astro app compiles them with TypeStyles.
See the full design spec:
docs/superpowers/specs/2026-07-18-var-ui-astro-design.md.
Install
pnpm add @var-ui/astro @var-ui/core astroPeer dependencies: astro, @var-ui/core. No react, react-aria-components,
or @var-ui/react.
TypeStyles setup
Recipe CSS must be extracted at build time with
@typestyles/vite. Add a side-effect
entry that imports @var-ui/core/styles, then point the plugin at it:
// typestyles-entry.ts
import '@var-ui/core/styles';// astro.config.mjs
import typestylesVite from '@typestyles/vite';
export default defineConfig({
vite: {
plugins: [typestylesVite({ extract: { modules: ['typestyles-entry.ts'] } })],
},
});Link the generated stylesheet in your layout (path depends on your build output):
<link rel="stylesheet" href="/typestyles.css" />See examples/astro-app for a working workspace setup.
ThemeScript
Add ThemeScript in <head> before styles to avoid a flash of unstyled content
when restoring color mode from localStorage:
---
import ThemeScript from '@var-ui/astro/ThemeScript';
// or: import { ThemeScript } from '@var-ui/astro';
import { defaultThemeClassName } from '@var-ui/core';
---
<html class={defaultThemeClassName}>
<head>
<ThemeScript themeClass={defaultThemeClassName} />
</head>
<body>...</body>
</html>Props:
themeClass(required) — theme class from@var-ui/core(e.g.defaultThemeClassName)storageKey(optional) —localStoragekey; defaults to'theme-mode'
The inline boot script matches the Astro snippet in @var-ui/core README: it reads
stored mode, falls back to prefers-color-scheme, adds the theme class, and sets or
clears data-mode on <html>.
ColorModeToggle
Self-contained segmented control for light/dark (and optionally system) mode. No React context required.
---
import ColorModeToggle from '@var-ui/astro/ColorModeToggle';
// or: import { ColorModeToggle } from '@var-ui/astro';
---
<ColorModeToggle />
<ColorModeToggle includeSystem appearance="labels" size="sm" />Props match the React ColorModeToggle: includeSystem, appearance
('icons' | 'labels' | 'iconsAndLabels'), size, className, aria-label, and
optional storageKey (default 'theme-mode').
Use the same storageKey on ThemeScript and ColorModeToggle so boot and toggle
stay in sync.
Components
Import from the package barrel:
---
import { Button, Alert, Stack, Tabs } from '@var-ui/astro';
---v0.1 ships a docs/content kit: layout primitives, feedback atoms, and content-site
components (Button, Link, CodeBlock, Steps, Breadcrumbs, Tabs,
Collapsible, …). Prop and variant names match @var-ui/react where an equivalent
exists; use named slots instead of React children for structured regions.
Tabs
Tabbed panels with a small vanilla controller for click and keyboard selection (ArrowLeft/Right, Home/End). No React Aria.
The first tab panel is visible without JavaScript via the native hidden attribute;
other panels start hidden.
---
import { Tabs } from '@var-ui/astro';
---
<Tabs tabs={[{ id: 'overview', label: 'Overview' }, { id: 'api', label: 'API' }]}>
<Fragment slot="overview">
<p>Overview content</p>
</Fragment>
<Fragment slot="api">
<p>API reference</p>
</Fragment>
</Tabs>Props:
tabs(required) —{ id: string; label: string }[]defaultSelectedId(optional) — tabidselected on load; defaults to the first tabclassName(optional)
Panel content uses named slots matching each tab id (e.g. slot="overview").
Pass defaultSelectedId to change which panel is shown before the client script runs.
Layout
Multi-pane page shell for header/footer bands and start/content/end zones. Static
bindings only — same core recipes as @var-ui/react, named slots instead of
compound props.
Interactive resize (useResizable + ResizeHandle) and responsive panel modes
(overlay / hidden) require React; Astro ships the structural shell and styling
only.
---
import {
Layout,
LayoutHeader,
LayoutFooter,
LayoutContent,
LayoutPanel,
Heading,
} from '@var-ui/astro';
---
<Layout padding={4}>
<LayoutHeader slot="header" hasDivider>
<Heading level={3}>Explorer</Heading>
</LayoutHeader>
<LayoutPanel slot="start" side="start" width={200} hasDivider>Nav</LayoutPanel>
<LayoutContent>Main content</LayoutContent>
<LayoutPanel slot="end" side="end" width={280} hasDivider>Inspector</LayoutPanel>
</Layout>Props mirror @var-ui/react where applicable, with these limitations:
LayoutPanelrequires an explicitside="start"orside="end"— Astro has no layout area context.LayoutacceptscontentWidth(px) to set the shell content-width CSS var on the root.- No automatic
data-divider-header/data-divider-footeron the layout root — sethasDivideronLayoutHeader/LayoutFooterdirectly.
Collapsible
Expand/collapse panel built on native <details> / <summary> (no React Aria).
---
import { Collapsible } from '@var-ui/astro';
---
<Collapsible title="Show code" defaultExpanded={false}>
<pre>…</pre>
</Collapsible>v0.1 omits controlled isExpanded / onExpandedChange and CollapsibleGroup; use
Accordion for multi-panel layouts or standalone Collapsible for a single panel.
Accordion
Multi-panel expand/collapse built on native <details> / <summary> with a small script for
single-open mode, keyboard navigation, and optional collapsible={false}.
---
import {
Accordion,
AccordionItem,
AccordionTrigger,
AccordionPanel,
} from '@var-ui/astro';
---
<Accordion type="single">
<AccordionItem id="billing" defaultExpanded>
<AccordionTrigger>Billing</AccordionTrigger>
<AccordionPanel>
<p>Update payment method and view invoices.</p>
</AccordionPanel>
</AccordionItem>
<AccordionItem id="shipping">
<AccordionTrigger>Shipping</AccordionTrigger>
<AccordionPanel>
<p>Manage delivery addresses and preferences.</p>
</AccordionPanel>
</AccordionItem>
</Accordion>AccordionItem also accepts a title prop instead of AccordionTrigger. Set
defaultExpanded on each item for the initial open state. When variant="flush" is used on
Accordion, pass variant="flush" to each AccordionItem as well.
Toast
Presentational toast markup for static docs or SSR previews:
---
import { Toast } from '@var-ui/astro';
---
<Toast tone="success" title="Saved" description="Your draft was stored." />For live notification queues without React, mount a ToastRegion and call the imperative
toast API from any client script:
---
import { ToastRegion } from '@var-ui/astro';
---
<ToastRegion placement="bottom-end" />
<button type="button" id="save-btn">Save</button>
<script>
import { toast } from '@var-ui/astro';
document.getElementById('save-btn')?.addEventListener('click', () => {
toast.show({
tone: 'success',
title: 'Saved',
description: 'Your draft was stored.',
});
});
</script>toast.show(), toast.update(id, patch), toast.dismiss(id), and toast.dismissAll() mirror
the React imperative API. toast() is shorthand for toast.show().
Interactivity ladder
Components climb only as far as needed — no React, no React Aria:
- Zero JS — styled markup + recipes.
- Native platform —
<button>/<a>,<details>, semantic lists. - Small vanilla TS — color mode, Tabs selection/keyboard; scripts enhance SSR markup.
- Zag.js (post–v0.1) — Dialog, Menu, Select, complex overlays.
Utilities
import { recipeProps, recipeClassName, cx } from '@var-ui/astro';See @var-ui/core for theme tokens and component recipes used by Astro components.
