Variants
Components in this registry define their variants with tailwind-variants, declared inline in the <script module> block of the .svelte file — see button.svelte for the reference example. There’s no separate *.variants.ts file; keeping tv() next to the markup it styles keeps the two in sync.
Pattern
1<script lang="ts" module>
2 import { type VariantProps, tv } from "tailwind-variants";
3 import { cn, type WithElementRef } from "$lib/utils.js";
4
5 export const buttonVariants = tv({
6 base: "inline-flex items-center rounded-md font-medium disabled:opacity-50",
7 variants: {
8 variant: {
9 default: "bg-primary text-primary-foreground hover:bg-primary/80",
10 outline: "border-border bg-background hover:bg-muted",
11 },
12 size: {
13 default: "h-9 px-4 text-sm",
14 sm: "h-8 px-3 text-xs",
15 },
16 },
17 defaultVariants: {
18 variant: "default",
19 size: "default",
20 },
21 });
22
23 export type ButtonVariant = VariantProps<typeof buttonVariants>["variant"];
24 export type ButtonSize = VariantProps<typeof buttonVariants>["size"];
25</script>
26
27<script lang="ts">
28 let { class: className, variant = "default", size = "default", ...restProps } = $props();
29</script>
30
31<button class={cn(buttonVariants({ variant, size }), className)} {...restProps} /> - Export the
tv()result (buttonVariants) so consumers and stories can call it directly. - Derive prop types from
VariantProps<typeof buttonVariants>, not a hand-written string union. - Compose the final class list with
cn()from$lib/utils.jsso caller-suppliedclasscan still override the recipe.
Checklist
- Use only
tvfromtailwind-variants— notclass-variance-authority/cva. - Keep the recipe in the same file as the component; don’t split it into a sibling
.variants.tsunless a future item genuinely needs to share one recipe across multiple components. - Export
VariantPropstype aliases next to the recipe. - List every source file the item needs (component + any shared recipe) in
_registry.svx’sfiles.