Guides
Extending Vesture
Installing a theme from @vesture/theme-retro is one way to restyle the library. Most teams don't want a second full theme, though — they want to override a handful of brand colors, compose the shipped components into their own patterns, or add a component the library doesn't ship. All three are first-class here, without forking anything.
Override tokens for part of a page
A full theme implements every slot in the contract. Sometimes you only want to change a few — a brand accent color inside one section, without touching the rest of the page. Vanilla-extract's assignInlineVars writes a CSS custom property directly, scoped to whatever element you put it on, and it composes with whichever theme is already active underneath:
import { assignInlineVars } from "@vanilla-extract/dynamic";
import { vars } from "@vesture/tokens";
import { Button } from "@vesture/react";
// Only override the tokens you care about — no need to
// implement the full contract like a real theme would.
const brandVars = assignInlineVars({
[vars.color.primary]: "#7c3aed",
[vars.color.primaryHover]: "#6d28d9",
[vars.color.primaryText]: "#ffffff",
});
function Section() {
return (
<div style={brandVars}>
{/* Every component in here reads the overridden primary color. */}
<Button>Create project</Button>
</div>
);
}Needs @vanilla-extract/dynamic — a small runtime package, no build plugin required (npm install @vanilla-extract/dynamic).
Compose your own patterns
There's no internal API to reach for — every component is a plain, composable building block. Every single-element component forwards its ref and merges (rather than replaces) any className you pass, so wrapping components in your own reusable patterns, or reaching for a utility class on top, both just work:
import { useId } from "react";
import { Input, Label, Stack } from "@vesture/react";
// A reusable pattern built entirely out of existing components —
// no internal API, just composition, refs, and ordinary props.
function FormField({ label, error, ...inputProps }) {
const id = useId();
return (
<Stack gap="xs">
<Label htmlFor={id} required>{label}</Label>
<Input id={id} invalid={!!error} {...inputProps} />
{error && <span className="text-sm text-red-600">{error}</span>}
</Stack>
);
}import { Button } from "@vesture/react";
// className is appended, not replaced — Tailwind, CSS modules,
// or a plain stylesheet class all layer on top of the component's
// own styling instead of fighting it.
<Button className="w-full">Full-width button</Button>Build a new component on the same tokens
Nothing about theming is special-cased to the components Vesture ships. vars from @vesture/tokens is a plain object of CSS custom property references — any component you write, using plain style props or your own .css.ts file, can read from it and will pick up whatever theme is active automatically:
import { vars } from "@vesture/tokens";
// A component we wrote ourselves — not part of @vesture/react —
// built by reading the same token contract every shipped
// component reads. It inherits whatever theme is active for free.
function Chip({ children }) {
return (
<span
style={{
display: "inline-flex",
padding: `${vars.space.xs} ${vars.space.sm}`,
borderRadius: vars.radius.full,
border: `${vars.border.width} ${vars.border.style} ${vars.color.borderStrong}`,
background: vars.color.surface,
color: vars.color.text,
fontSize: vars.font.sizeSm,
}}
>
{children}
</span>
);
}Toggle the theme in the header on this page and the chip above repaints with it — it never imports a color, it just reads the contract.
Want a full custom theme instead?
If you're restyling everything — not just a section — see Theming for how to implement the full token contract with createTheme, the same way @vesture/theme-retro does.