Vesture
Get started

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:

Unscoped — current theme
Draft
Scoped — brand override
Draft
tsx
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:

tsx
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>
  );
}
className="w-full shadow-[…]" merged onto Vesture's own classes — Tailwind utilities from this site, layout classes from the component.
tsx
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:

Design systemZero runtimeAccessible
tsx
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.