# Button

Click target with eight variants: primary, secondary, glow, ghost, quiet, outline, destructive, and panel. Primary, secondary, and destructive are flat fills; glow is the lit headline action. Quiet matches ghost text color without a hover fill. Supports four sizes and stable loading, success, and error states. Renders as <button> by default or <a> when `href` is provided.

- Package: `@mielui/svelte`
- Component version: `5.0.0`
- Depends on Mielui components: _internal/utils
- Shared utilities: utils.cn, utils.Intent

## Install

~~~~sh
pnpm dlx @mielui/svelte add button
~~~~

## API

This reference is generated at build time from the component manifest, public `index.ts`, and documentation examples below. Changes to those source files are reflected here in the published Markdown. Standard Svelte and HTML attributes accepted by the exported prop types are supported.


### Button

| Prop | Type | Default | Required | Bindable | Description |
| --- | --- | --- | --- | --- | --- |
| disabled | boolean \| undefined | false | No | No | Prevents activation. A disabled link loses its destination and is skipped by Tab. |
| variant | ButtonVariant \| undefined | 'primary' | No | No | Visual style. Choose it for what the action means, not for decoration. |
| size | "sm" \| "md" \| "lg" \| "icon" \| undefined | 'md' | No | No | Control height. `icon` is a square button for a single icon. |
| children | Snippet<[]> \| undefined | - | No | No | Content rendered inside. |
| element | HTMLButtonElement \| HTMLAnchorElement \| undefined | - | No | Yes | Bindable reference to the rendered DOM element. Type is the union of the two possible element types -- narrow at the use site: |
| unstyled | boolean \| undefined | false | No | No | Skip the variant/size base classes and render with `class` alone. |
| status | ButtonStatus \| undefined | - | No | No | Controlled visual state. Loading remains focusable and refuses activation. |
| loading | boolean \| undefined | - | No | No | Convenience alias for `status="loading"`. |
| labels | ButtonLabels \| undefined | - | No | No | Overrides the built-in text and accessible names. Every key is optional and English is the fallback. |
| onclick | ((event: MouseEvent) => void) \| undefined | - | No | No | Called when the button is activated. Loading and disabled buttons do not call it. |
| onkeydown | ((event: KeyboardEvent) => void) \| undefined | - | No | No | Called on key down, before the button handles the key itself. |
| href | string \| undefined | - | No | No | Renders a link to this address instead of a button. |

Also accepts the native HTML attributes and events listed in the rendered API reference.

### Public types

~~~~ts
import type { Intent } from '@mielui/svelte/utils';
import type { Snippet } from 'svelte';
import type { HTMLAnchorAttributes, HTMLButtonAttributes } from 'svelte/elements';
import Button from './button.svelte';

export type ButtonVariant = Intent | 'glow' | 'panel' | 'quiet';
export type ButtonStatus = 'idle' | 'loading' | 'success' | 'error';

export type ButtonLabels = {
    /** Text shown while `status` is `loading`. The button keeps its width. */
    loading?: string;
    /** Text shown and announced while `status` is `success`. */
    success?: string;
    /** Text shown and announced while `status` is `error`. */
    error?: string;
};

type ButtonSharedProps = {
    /** Prevents activation. A disabled link loses its destination and is skipped by Tab. */
    disabled?: boolean;
    /** Visual style. Choose it for what the action means, not for decoration. */
    variant?: ButtonVariant;
    /** Control height. `icon` is a square button for a single icon. */
    size?: 'sm' | 'md' | 'lg' | 'icon';
    /** Content rendered inside. */
    children?: Snippet;
    /**
     * Bindable reference to the rendered DOM element. Type is the union of
     * the two possible element types -- narrow at the use site:
     *
     * ```ts
     * let buttonEl: HTMLButtonElement | HTMLAnchorElement | undefined =
     *   $state();
     * // ...
     * <Button bind:element={buttonEl} href={undefined} />
     * if (buttonEl instanceof HTMLButtonElement) {
     *   buttonEl.focus();
     * }
     * ```
     *
     * Pass `href={undefined}` to guarantee a `<button>` element and narrow
     * to `HTMLButtonElement`; pass `href` to render an `<a>` element. The
     * union exists because both element types share the public API surface;
     * the type system can't distinguish without flow-sensitive analysis at
     * the call site.
     */
    element?: HTMLButtonElement | HTMLAnchorElement | undefined;
    /**
     * Skip the variant/size base classes and render with `class` alone.
     *
     * Menu rows use this to opt out of the button's sizing utilities so the
     * `mielui-menu-item` stylesheet contract governs the surface instead --
     * utilities outrank the `components` layer, so the two cannot coexist.
     */
    unstyled?: boolean;
    /** Controlled visual state. Loading remains focusable and refuses activation. */
    status?: ButtonStatus;
    /** Convenience alias for `status="loading"`. */
    loading?: boolean;
    /**
     * Overrides the built-in text and accessible names. Every key is optional and English is the
     * fallback.
     */
    labels?: ButtonLabels;
    /** Called when the button is activated. Loading and disabled buttons do not call it. */
    onclick?: (event: MouseEvent) => void;
    /** Called on key down, before the button handles the key itself. */
    onkeydown?: (event: KeyboardEvent) => void;
};

export type ButtonProps = ButtonSharedProps &
    (
        | ({
              /** Renders a link to this address instead of a button. */
              href: string;
          } & Omit<HTMLAnchorAttributes, keyof ButtonSharedProps | 'href'>)
        | ({
              /** Renders a link to this address instead of a button. */
              href?: undefined;
          } & Omit<HTMLButtonAttributes, keyof ButtonSharedProps>)
    );

export { Button };
export default Button;
~~~~

## Examples

### As Link

~~~~svelte
<script lang="ts">
    import { LinkSquare02Icon as External } from '@hugeicons/core-free-icons';
    import { Button } from '@mielui/svelte/components/button';
    import HugeiconsIcon from '@mielui/svelte/hugeicons-icon';
</script>

<Button href="/docs" variant="outline">
    <HugeiconsIcon icon={External} size={13} />
    Open docs
</Button>
~~~~

### Disabled

~~~~svelte
<script lang="ts">
    import { Button } from '@mielui/svelte/components/button';
</script>

<Button disabled>Disabled</Button>
~~~~

### Icon Group

~~~~svelte
<script lang="ts">
    import { Copy01Icon, Delete02Icon, Download01Icon } from '@hugeicons/core-free-icons';
    import { Button } from '@mielui/svelte/components/button';
    import * as Tooltip from '@mielui/svelte/components/tooltip';
    import HugeiconsIcon from '@mielui/svelte/hugeicons-icon';

    let removed = $state(false);
    let message = $state('');

    function download() {
        const link = document.createElement('a');
        const url = URL.createObjectURL(
            new Blob(['Release notes\nAll checks passed.'], { type: 'text/plain' })
        );
        link.href = url;
        link.download = 'release-notes.txt';
        link.click();
        URL.revokeObjectURL(url);
        message = 'Sample download started.';
    }

    async function copy() {
        try {
            await navigator.clipboard.writeText('Release notes: all checks passed.');
            message = 'Release notes copied.';
        } catch {
            message = 'Clipboard access is unavailable.';
        }
    }

    function remove() {
        removed = !removed;
        message = removed ? 'Attachment removed from this preview.' : 'Attachment restored.';
    }
</script>

<div class="flex flex-col items-center gap-3">
    <div class="flex items-center gap-2">
        <Tooltip.Root>
            <Tooltip.Trigger>
                <Button variant="ghost" size="icon" aria-label="Download sample" onclick={download}>
                    <HugeiconsIcon icon={Download01Icon} size={16} />
                </Button>
            </Tooltip.Trigger>
            <Tooltip.Content>Download sample</Tooltip.Content>
        </Tooltip.Root>
        <Tooltip.Root>
            <Tooltip.Trigger>
                <Button variant="ghost" size="icon" aria-label="Copy release notes" onclick={copy}>
                    <HugeiconsIcon icon={Copy01Icon} size={16} />
                </Button>
            </Tooltip.Trigger>
            <Tooltip.Content>Copy release notes</Tooltip.Content>
        </Tooltip.Root>
        <Tooltip.Root>
            <Tooltip.Trigger>
                <Button
                    variant="ghost"
                    size="icon"
                    aria-label={removed ? 'Restore attachment' : 'Remove attachment'}
                    aria-pressed={removed}
                    onclick={remove}
                >
                    <HugeiconsIcon icon={Delete02Icon} size={16} />
                </Button>
            </Tooltip.Trigger>
            <Tooltip.Content>
                {removed ? 'Restore attachment' : 'Remove attachment'}
            </Tooltip.Content>
        </Tooltip.Root>
    </div>
    <p role="status" class="text-sm text-foreground-muted">{message}</p>
</div>
~~~~

### Leading Icon

~~~~svelte
<script lang="ts">
    import { Add01Icon as Plus } from '@hugeicons/core-free-icons';
    import { Button } from '@mielui/svelte/components/button';
    import HugeiconsIcon from '@mielui/svelte/hugeicons-icon';
</script>

<Button>
    <HugeiconsIcon icon={Plus} size={14} />
    New project
</Button>
~~~~

### Loading

~~~~svelte
<script lang="ts">
    import type { ButtonStatus } from '@mielui/svelte/components/button';
    import { Button } from '@mielui/svelte/components/button';
    import { Switch } from '@mielui/svelte/components/switch';
    import { onDestroy } from 'svelte';

    let status = $state<ButtonStatus>('idle');
    let failNext = $state(false);
    let timer: ReturnType<typeof setTimeout> | undefined;

    function publish() {
        if (status === 'loading') {
            return;
        }
        clearTimeout(timer);
        const shouldFail = failNext;
        failNext = false;
        status = 'loading';
        timer = setTimeout(() => {
            if (shouldFail) {
                status = 'error';
                return;
            }
            status = 'success';
            timer = setTimeout(() => {
                status = 'idle';
            }, 1200);
        }, 1100);
    }
    onDestroy(() => {
        clearTimeout(timer);
    });
</script>

<div class="flex flex-col items-start gap-4">
    <Switch bind:checked={failNext} disabled={status === 'loading'} label="Fail the next attempt" />
    <Button
        {status}
        labels={{
            loading: 'Publishing…',
            success: 'Published',
            error: 'Retry'
        }}
        onclick={publish}
    >
        Publish
    </Button>
    <p class="text-sm text-foreground-muted">
        This preview simulates a request. It publishes nothing.
    </p>
</div>
~~~~

### Sizes

~~~~svelte
<script lang="ts">
    import { FavouriteIcon as Heart } from '@hugeicons/core-free-icons';
    import { Button } from '@mielui/svelte/components/button';
    import * as Tooltip from '@mielui/svelte/components/tooltip';
    import HugeiconsIcon from '@mielui/svelte/hugeicons-icon';
</script>

<div class="flex flex-wrap items-center justify-center gap-4">
    <Button size="sm">Small</Button>
    <Button>Medium</Button>
    <Button size="lg">Large</Button>
    <Tooltip.Root>
        <Tooltip.Trigger>
            <Button size="icon" aria-label="Favorite">
                <HugeiconsIcon icon={Heart} size={14} />
            </Button>
        </Tooltip.Trigger>
        <Tooltip.Content>Favorite</Tooltip.Content>
    </Tooltip.Root>
</div>
~~~~

### Trailing Icon

~~~~svelte
<script lang="ts">
    import { ArrowRight02Icon as ArrowRight } from '@hugeicons/core-free-icons';
    import { Button } from '@mielui/svelte/components/button';
    import HugeiconsIcon from '@mielui/svelte/hugeicons-icon';
</script>

<Button variant="secondary">
    Continue
    <HugeiconsIcon icon={ArrowRight} size={14} />
</Button>
~~~~

### Variant Glow

~~~~svelte
<script lang="ts">
    import { Button } from '@mielui/svelte/components/button';
</script>

<div class="flex flex-wrap items-center justify-center gap-3">
    <Button variant="glow">Get started</Button>
    <Button
        variant="glow"
        class="text-background [--mielui-glow-color:var(--color-foreground)] [--mielui-glow-light:0.3] dark:[--mielui-glow-light:1]"
    >
        Foreground
    </Button>
    <Button variant="glow" class="text-[#1c2002] [--mielui-glow-color:#e4f222]"> Lime </Button>
</div>
~~~~

### Variants

~~~~svelte
<script lang="ts">
    import { Button } from '@mielui/svelte/components/button';
</script>

<div class="flex max-w-lg flex-wrap items-center justify-center gap-3">
    <Button variant="primary">Primary</Button>
    <Button variant="secondary">Secondary</Button>
    <Button variant="outline">Outline</Button>
    <Button variant="ghost">Ghost</Button>
    <Button variant="quiet">Quiet</Button>
    <Button variant="destructive">Destructive</Button>
    <Button variant="panel">Panel</Button>
    <Button variant="glow">Glow</Button>
</div>
~~~~

For the rendered reference, visit [/docs/components/button](/docs/components/button).
