For Svelte 5 and SvelteKit apps built with shadcn-svelte and Tailwind.
npm i @gistui/svelte @gistui/styles @gistui/catalogyarn add @gistui/svelte @gistui/styles @gistui/catalogpnpm add @gistui/svelte @gistui/styles @gistui/catalogbun add @gistui/svelte @gistui/styles @gistui/catalog1. Order the CSS
GistUI’s stylesheet must come before Tailwind, so the cascade layers are theme, base, gistui, components, utilities: above Tailwind’s reset, below its utilities.
<script>
import "@gistui/styles/styles.css";
import "../app.css"; // @import "tailwindcss"; and the shadcn-svelte theme
let { children } = $props();
</script>
{@render children()}2. Match the theme with tokens
shadcn-svelte uses the same CSS variables as shadcn/ui.
export const tokens = {
font: "inherit",
radius: 10,
bg: "var(--background)",
surface: "var(--card)",
surfaceSunk: "var(--muted)",
surfaceHover: "var(--accent)",
fg: "var(--foreground)",
fgMuted: "var(--muted-foreground)",
border: "var(--border)",
borderStrong: "var(--input)",
primary: "var(--primary)",
primaryFg: "var(--primary-foreground)",
accent: "var(--primary)",
accentFg: "var(--primary-foreground)",
danger: "var(--destructive)",
chart: ["var(--chart-1)", "var(--chart-2)", "var(--chart-3)", "var(--chart-4)", "var(--chart-5)"],
};3. Wrap components
<script lang="ts">
import { GistChildren, type GistUIComponentProps } from "@gistui/svelte";
import * as Card from "$lib/components/ui/card";
let {}: GistUIComponentProps = $props();
</script>
<Card.Root data-gistui="Card">
<Card.Content class="flex flex-col gap-4 p-6"><GistChildren /></Card.Content>
</Card.Root><script lang="ts">
import { gistField, type GistUIComponentProps } from "@gistui/svelte";
import { Input } from "$lib/components/ui/input";
import { Label } from "$lib/components/ui/label";
let p: GistUIComponentProps = $props();
const id = $props.id();
const field = gistField(() => p.ctx); // validation, error, bind:$var
</script>
<div class="grid gap-2">
{#if field.current.label}<Label for={id}>{field.current.label}</Label>{/if}
<Input
{id}
name={field.current.name}
type={(p.props.type as string) ?? "text"}
placeholder={p.props.placeholder as string}
disabled={field.current.locked}
aria-invalid={field.current.error ? true : undefined}
value={field.current.bound ? String(field.current.value ?? "") : (p.props.value as string)}
oninput={(e) => field.current.bound && field.current.setValue(e.currentTarget.value)}
/>
{#if field.current.error}<p role="alert" class="text-sm text-destructive">{field.current.error}</p>{/if}
</div>Call gistField while the component initialises, as shown.
4. Render with them
<script lang="ts">
import { GistUI } from "@gistui/svelte";
import GistCard from "./GistCard.svelte";
import GistInput from "./GistInput.svelte";
import { tokens } from "./tokens";
</script>
<GistUI source={answer} streaming={loading} components={{ Card: GistCard, Input: GistInput }} {tokens} onaction={onAction} />Notes
- Keep GistUI’s built-in
Button. With the tokens above it matches your buttons, and every action, form submit and dialog keeps working. - For a checkbox or switch, call
gistField(() => p.ctx, "bool"). - shadcn-svelte is built on Bits UI: for the primitives directly, see Bits UI.