Updated: We have updated the button component to add new sizes: icon-sm and icon-lg. See the changelog for more details. Follow the
instructions to update your project.
<script lang="ts">
import ArrowUpIcon from "@lucide/svelte/icons/arrow-up";
import { Button } from "$lib/components/ui/button/index.js";
</script>
<div class="flex flex-wrap items-center gap-2 md:flex-row">
<Button variant="outline">Button</Button>
<Button variant="outline" size="icon" aria-label="Submit">
<ArrowUpIcon />
</Button>
</div> <Button variant="outline">Button</Button>
<Button variant="outline" size="icon" aria-label="Submit">
<ArrowUpIcon />
</Button> Installation
pnpm dlx shadcn-svelte@latest add button Copy and paste the following code into your project.
<script lang="ts" module>
import { cn, type WithElementRef } from '$UTILS$.js';
import type { HTMLAnchorAttributes, HTMLButtonAttributes } from 'svelte/elements';
import { type VariantProps, tv } from 'tailwind-variants';
export const buttonVariants = tv({
base: "focus-visible:border-zinc-300 focus-visible:ring-zinc-300/30 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 rounded-full border border-transparent bg-clip-padding font-mono text-xs font-semibold tracking-widest uppercase shadow-none focus-visible:ring-2 active:not-aria-[haspopup]:translate-y-px aria-invalid:ring-2 [&_svg:not([class*='size-'])]:size-3.5 group/button inline-flex shrink-0 items-center justify-center whitespace-nowrap transition-all duration-200 outline-none select-none disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
variants: {
variant: {
default: 'bg-[#b9d765] text-[#101207] hover:bg-[#d0e891]',
outline:
'border-zinc-800 bg-background text-zinc-50 hover:border-zinc-700 hover:bg-zinc-900 aria-expanded:border-zinc-700 aria-expanded:bg-zinc-900',
secondary:
'border-zinc-800 bg-zinc-50 text-background hover:bg-zinc-200 aria-expanded:bg-zinc-200',
ghost:
'bg-transparent text-zinc-300 hover:bg-zinc-100 hover:text-background dark:hover:bg-zinc-100 aria-expanded:bg-zinc-100 aria-expanded:text-background',
destructive:
'border-destructive/40 bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/50 focus-visible:ring-destructive/25 dark:bg-destructive/20 dark:hover:bg-destructive/30',
link: 'rounded-none px-0 text-[#d0e891] underline underline-offset-4 hover:text-zinc-50'
},
size: {
default:
'h-9 gap-1.5 px-5 has-data-[icon=inline-end]:pr-4 has-data-[icon=inline-start]:pl-4',
xs: "h-7 gap-1 px-3 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2 [&_svg:not([class*='size-'])]:size-3",
sm: 'h-8 gap-1 px-4 has-data-[icon=inline-end]:pr-3 has-data-[icon=inline-start]:pl-3',
lg: 'h-10 gap-1.5 px-7 has-data-[icon=inline-end]:pr-5 has-data-[icon=inline-start]:pl-5',
icon: 'size-9',
'icon-xs': "size-7 [&_svg:not([class*='size-'])]:size-3",
'icon-sm': 'size-8',
'icon-lg': 'size-10'
}
},
defaultVariants: {
variant: 'default',
size: 'default'
}
});
export type ButtonVariant = VariantProps<typeof buttonVariants>['variant'];
export type ButtonSize = VariantProps<typeof buttonVariants>['size'];
export type ButtonProps = WithElementRef<HTMLButtonAttributes> &
WithElementRef<HTMLAnchorAttributes> & {
variant?: ButtonVariant;
size?: ButtonSize;
};
</script>
<script lang="ts">
let {
class: className,
variant = 'default',
size = 'default',
ref = $bindable(null),
href = undefined,
type = 'button',
disabled,
children,
...restProps
}: ButtonProps = $props();
</script>
{#if href}
<a
bind:this={ref}
data-slot="button"
class={cn(buttonVariants({ variant, size }), className)}
href={disabled ? undefined : href}
aria-disabled={disabled}
role={disabled ? 'link' : undefined}
tabindex={disabled ? -1 : undefined}
{...restProps}
>
{@render children?.()}
</a>
{:else}
<button
bind:this={ref}
data-slot="button"
class={cn(buttonVariants({ variant, size }), className)}
{type}
{disabled}
{...restProps}
>
{@render children?.()}
</button>
{/if}
import Root, {
type ButtonProps,
type ButtonSize,
type ButtonVariant,
buttonVariants
} from './button.svelte';
export {
Root,
type ButtonProps as Props,
//
Root as Button,
buttonVariants,
type ButtonProps,
type ButtonSize,
type ButtonVariant
};
Usage
<script lang="ts">
import { Button } from '$lib/components/ui/button/index.js';
</script>
<Button variant="outline">Button</Button> Examples
Size
<script lang="ts">
import ArrowUpRightIcon from "@lucide/svelte/icons/arrow-up-right";
import { Button } from "$lib/components/ui/button/index.js";
</script>
<div class="flex flex-col items-start gap-8 sm:flex-row">
<div class="flex items-start gap-2">
<Button size="sm" variant="outline">Small</Button>
<Button size="icon-sm" aria-label="Submit" variant="outline">
<ArrowUpRightIcon />
</Button>
</div>
<div class="flex items-start gap-2">
<Button variant="outline">Default</Button>
<Button size="icon" aria-label="Submit" variant="outline">
<ArrowUpRightIcon />
</Button>
</div>
<div class="flex items-start gap-2">
<Button variant="outline" size="lg">Large</Button>
<Button size="icon-lg" aria-label="Submit" variant="outline">
<ArrowUpRightIcon />
</Button>
</div>
</div> <!-- Small -->
<Button size="sm" variant="outline">Small</Button>
<Button size="icon-sm" aria-label="Submit" variant="outline">
<ArrowUpRightIcon />
</Button>
<!-- Medium -->
<Button variant="outline">Default</Button>
<Button size="icon" aria-label="Submit" variant="outline">
<ArrowUpRightIcon />
</Button>
<!-- Large -->
<Button size="lg" variant="outline">Large</Button>
<Button size="icon-lg" aria-label="Submit" variant="outline">
<ArrowUpRightIcon />
</Button> Default
<script lang="ts">
import { Button } from "$lib/components/ui/button/index.js";
</script>
<Button>Button</Button> <Button>Button</Button> Outline
<script lang="ts">
import { Button } from "$lib/components/ui/button/index.js";
</script>
<Button variant="outline">Outline</Button> <Button variant="outline">Outline</Button> Secondary
<script lang="ts">
import { Button } from "$lib/components/ui/button/index.js";
</script>
<Button variant="secondary">Secondary</Button> <Button variant="secondary">Secondary</Button> Ghost
<script lang="ts">
import { Button } from "$lib/components/ui/button/index.js";
</script>
<Button variant="ghost">Ghost</Button> <Button variant="ghost">Ghost</Button> Destructive
<script lang="ts">
import { Button } from "$lib/components/ui/button/index.js";
</script>
<Button variant="destructive">Destructive</Button> <Button variant="destructive">Destructive</Button> Link
<script lang="ts">
import { Button } from "$lib/components/ui/button/index.js";
</script>
<Button variant="link">Link</Button> <Button variant="link">Link</Button> Icon
<script lang="ts">
import CircleFadingArrowUpIcon from "@lucide/svelte/icons/circle-fading-arrow-up";
import { Button } from "$lib/components/ui/button/index.js";
</script>
<Button variant="outline" size="icon" aria-label="Submit">
<CircleFadingArrowUpIcon />
</Button> <Button variant="outline" size="icon" aria-label="Submit">
<CircleFadingArrowUpIcon />
</Button> With Icon
The spacing between the icon and the text is automatically adjusted based on the size of the button. You do not need any margin on the icon.
<script lang="ts">
import IconGitBranch from "@lucide/svelte/icons/git-branch";
import { Button } from "$lib/components/ui/button/index.js";
</script>
<Button variant="outline" size="sm">
<IconGitBranch /> New Branch
</Button> <Button variant="outline" size="sm">
<IconGitBranch /> New Branch
</Button> Rounded
Use the rounded-full class to make the button rounded.
<script lang="ts">
import ArrowUpIcon from "@lucide/svelte/icons/arrow-up";
import { Button } from "$lib/components/ui/button/index.js";
</script>
<div class="flex flex-col gap-8">
<Button variant="outline" size="icon" class="rounded-full">
<ArrowUpIcon />
</Button>
</div> <Button variant="outline" size="icon" className="rounded-full">
<ArrowUpRightIcon />
</Button> Spinner
<script lang="ts">
import { Button } from "$lib/components/ui/button/index.js";
import { Spinner } from "$lib/components/ui/spinner/index.js";
</script>
<Button size="sm" variant="outline" disabled>
<Spinner />
Submit
</Button> <Button size="sm" variant="outline" disabled>
<Spinner />
Submit
</Button> Button Group
To create a button group, use the ButtonGroup component. See the Button Group documentation for more details.
<script lang="ts">
import Archive from "@lucide/svelte/icons/archive";
import ArrowLeft from "@lucide/svelte/icons/arrow-left";
import CalendarPlus from "@lucide/svelte/icons/calendar-plus";
import Clock from "@lucide/svelte/icons/clock";
import ListFilter from "@lucide/svelte/icons/list-filter";
import MailCheck from "@lucide/svelte/icons/mail-check";
import MoreHorizontal from "@lucide/svelte/icons/more-horizontal";
import Tag from "@lucide/svelte/icons/tag";
import Trash2 from "@lucide/svelte/icons/trash-2";
import { Button } from "$lib/components/ui/button/index.js";
import * as ButtonGroup from "$lib/components/ui/button-group/index.js";
import * as DropdownMenu from "$lib/components/ui/dropdown-menu/index.js";
let label = $state("personal");
</script>
<ButtonGroup.Root>
<ButtonGroup.Root class="hidden sm:flex">
<Button variant="outline" size="icon-sm" aria-label="Go Back">
<ArrowLeft />
</Button>
</ButtonGroup.Root>
<ButtonGroup.Root>
<Button size="sm" variant="outline">Archive</Button>
<Button size="sm" variant="outline">Report</Button>
</ButtonGroup.Root>
<ButtonGroup.Root>
<Button size="sm" variant="outline">Snooze</Button>
<DropdownMenu.Root>
<DropdownMenu.Trigger>
{#snippet child({ props })}
<Button
{...props}
variant="outline"
size="icon-sm"
aria-label="More Options"
>
<MoreHorizontal />
</Button>
{/snippet}
</DropdownMenu.Trigger>
<DropdownMenu.Content align="end" class="w-52">
<DropdownMenu.Group>
<DropdownMenu.Item>
<MailCheck />
Mark as Read
</DropdownMenu.Item>
<DropdownMenu.Item>
<Archive />
Archive
</DropdownMenu.Item>
</DropdownMenu.Group>
<DropdownMenu.Separator />
<DropdownMenu.Group>
<DropdownMenu.Item>
<Clock />
Snooze
</DropdownMenu.Item>
<DropdownMenu.Item>
<CalendarPlus />
Add to Calendar
</DropdownMenu.Item>
<DropdownMenu.Item>
<ListFilter />
Add to List
</DropdownMenu.Item>
<DropdownMenu.Sub>
<DropdownMenu.SubTrigger>
<Tag />
Label As...
</DropdownMenu.SubTrigger>
<DropdownMenu.SubContent>
<DropdownMenu.RadioGroup bind:value={label}>
<DropdownMenu.RadioItem value="personal"
>Personal</DropdownMenu.RadioItem
>
<DropdownMenu.RadioItem value="work"
>Work</DropdownMenu.RadioItem
>
<DropdownMenu.RadioItem value="other"
>Other</DropdownMenu.RadioItem
>
</DropdownMenu.RadioGroup>
</DropdownMenu.SubContent>
</DropdownMenu.Sub>
</DropdownMenu.Group>
<DropdownMenu.Separator />
<DropdownMenu.Group>
<DropdownMenu.Item class="text-destructive focus:text-destructive">
<Trash2 />
Trash
</DropdownMenu.Item>
</DropdownMenu.Group>
</DropdownMenu.Content>
</DropdownMenu.Root>
</ButtonGroup.Root>
</ButtonGroup.Root> <ButtonGroup.Root>
<ButtonGroup.Root class="hidden sm:flex">
<Button variant="outline" size="icon" aria-label="Go Back">
<ArrowLeft />
</Button>
</ButtonGroup.Root>
<ButtonGroup.Root>
<Button variant="outline">Archive</Button>
<Button variant="outline">Report</Button>
</ButtonGroup.Root>
<ButtonGroup.Root>
<Button variant="outline">Snooze</Button>
<DropdownMenu.Root>
<DropdownMenu.Trigger>
{#snippet child({ props })}
<Button {...props} variant="outline" size="icon" aria-label="More Options">
<MoreHorizontal />
</Button>
{/snippet}
</DropdownMenu.Trigger>
<DropdownMenu.Content align="end" class="w-52">
<DropdownMenu.Group>
<DropdownMenu.Item>
<MailCheck />
Mark as Read
</DropdownMenu.Item>
<DropdownMenu.Item>
<Archive />
Archive
</DropdownMenu.Item>
</DropdownMenu.Group>
<DropdownMenu.Separator />
<DropdownMenu.Group>
<DropdownMenu.Item>
<Clock />
Snooze
</DropdownMenu.Item>
<DropdownMenu.Item>
<CalendarPlus />
Add to Calendar
</DropdownMenu.Item>
<DropdownMenu.Item>
<ListFilter />
Add to List
</DropdownMenu.Item>
<DropdownMenu.Sub>
<DropdownMenu.SubTrigger>
<Tag />
Label As...
</DropdownMenu.SubTrigger>
<DropdownMenu.SubContent>
<DropdownMenu.RadioGroup bind:value={label}>
<DropdownMenu.RadioItem value="personal">Personal</DropdownMenu.RadioItem>
<DropdownMenu.RadioItem value="work">Work</DropdownMenu.RadioItem>
<DropdownMenu.RadioItem value="other">Other</DropdownMenu.RadioItem>
</DropdownMenu.RadioGroup>
</DropdownMenu.SubContent>
</DropdownMenu.Sub>
</DropdownMenu.Group>
<DropdownMenu.Separator />
<DropdownMenu.Group>
<DropdownMenu.Item class="text-destructive focus:text-destructive">
<Trash2 />
Trash
</DropdownMenu.Item>
</DropdownMenu.Group>
</DropdownMenu.Content>
</DropdownMenu.Root>
</ButtonGroup.Root>
</ButtonGroup.Root> Link
You can convert the <button> into an <a> element by simply passing an href as a prop.
<script lang="ts">
import { Button } from '$lib/components/ui/button/index.js';
</script>
<Button href="/docs">Dashboard</Button> Alternatively, you can use the buttonVariants helper to create a link that looks like a button.
<script lang="ts">
import { buttonVariants } from '$lib/components/ui/button';
</script>
<a href="/docs" class={buttonVariants({ variant: 'outline' })}> Dashboard </a> Changelog
2025-09-24 New sizes
We have added two new sizes to the button component: icon-sm and icon-lg. These sizes are used to create icon buttons. To add them, edit button.svelte and add the following code under size in buttonVariants:
export const buttonVariants = tv({
// ...
variants: {
// ...
size: {
// ...
icon: 'size-9',
'icon-sm': 'size-8',
'icon-lg': 'size-10'
}
}
});