Button

Displays a button or a component that looks like a button.

Variants & Sizes
Icon Right
Icon Left
Icon Only
Invalid States
Examples
Link

Installation

Install the dependencies

pnpm add cn class-variance-authority github:sveltery/base

Copy the component

Copy the following files into src/lib/components/ui/button.

src/lib/components/ui/button/Button.svelte
<script lang="ts">
  import { Button as Primitive } from '@sveltery/base/button';
  import type { ButtonProps } from './types.js';
  import { buttonVariants } from './variants.js';
  import { cn } from 'cn';
  let { children, class: classProp, variant = 'default', size = 'default', ...props }: ButtonProps = $props();
</script>
<Primitive data-slot="button" {...props} {children} class={cn(buttonVariants({ variant, size, class: classProp }))} />
src/lib/components/ui/button/types.ts
import type { ButtonProps as BaseButtonProps } from '@sveltery/base/button';
import type { ButtonVariants } from './variants.js';
export type ButtonProps = BaseButtonProps & ButtonVariants;
export type { ButtonState } from '@sveltery/base/button';
src/lib/components/ui/button/variants.ts
// Adapted from shadcn-ui/ui d75a96ab; MIT: packages/ui/THIRD_PARTY_NOTICES.md.
import { cva } from 'class-variance-authority';
import type { VariantProps } from 'class-variance-authority';
export const buttonVariants = cva("focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 rounded-lg border border-transparent bg-clip-padding text-sm font-medium focus-visible:ring-3 aria-invalid:ring-3 active:not-aria-[haspopup]:translate-y-px [&_svg:not([class*='size-'])]:size-4 group/button inline-flex shrink-0 items-center justify-center whitespace-nowrap transition-all outline-none select-none disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0", {
  variants: {
    variant: {
      default: 'bg-primary text-primary-foreground hover:bg-primary/80', outline: 'border-border bg-background hover:bg-muted hover:text-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50 aria-expanded:bg-muted aria-expanded:text-foreground',
      secondary: 'bg-secondary text-secondary-foreground hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)] aria-expanded:bg-secondary aria-expanded:text-secondary-foreground', ghost: 'hover:bg-muted hover:text-foreground dark:hover:bg-muted/50 aria-expanded:bg-muted aria-expanded:text-foreground',
      destructive: 'bg-destructive/10 hover:bg-destructive/20 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/20 text-destructive focus-visible:border-destructive/40 dark:hover:bg-destructive/30', link: 'text-primary underline-offset-4 hover:underline',
    },
    size: {
      default: 'h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2', xs: "h-6 gap-1 rounded-[min(var(--radius-md),10px)] px-2 text-xs in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3", sm: "h-7 gap-1 rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5", lg: 'h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',
      icon: 'size-8', 'icon-xs': "size-6 rounded-[min(var(--radius-md),10px)] in-data-[slot=button-group]:rounded-lg [&_svg:not([class*='size-'])]:size-3", 'icon-sm': 'size-7 rounded-[min(var(--radius-md),12px)] in-data-[slot=button-group]:rounded-lg', 'icon-lg': 'size-9',
    },
  },
  defaultVariants: { variant: 'default', size: 'default' },
});
export type ButtonVariants = VariantProps<typeof buttonVariants>;
src/lib/components/ui/button/index.ts
export { default as Button } from './Button.svelte';
export { buttonVariants } from './variants.js';
export type { ButtonVariants } from './variants.js';
export type { ButtonProps, ButtonState } from './types.js';

Update the import paths

Change the imports to match your project setup.

Usage

<script lang="ts">
  import { Button } from '$lib/components/ui/button';
</script>
<Button variant="outline">Button</Button>

API reference

Button is styled on top of the Sveltery Base Button primitive, which owns its behavior, focus handling and accessibility. See the Base UI Button documentation for the behavior it ports.