Svelte 5 component · fractalsvelte-ui · Actions
IconButton
A compact icon-only action button with accessible screen-reader labelling and hover tooltips. Fits standard icon sizes across five scale tiers.
Install
IconButton is a Svelte 5 icon button component in @fractaldesign/fractalsvelte-ui. It works in SvelteKit and in any Svelte 5 app.
pnpm add @fractaldesign/fractalsvelte-uiImport
import { IconButton } from '@fractaldesign/fractalsvelte-ui';Usage
<IconButton variant={'secondary'} size={'md'}>Click me</IconButton>Props
Also accepts every attribute of HTMLButtonAttributes (without type), spread onto
the root element.
| Name | Type | Default |
|---|---|---|
label | string | — |
variant | Variant 'primary' | 'secondary' | 'soft' | 'outline' | 'ghost' | 'destructive' | 'surface' | 'secondary' |
size | Size 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'md' |
shape | Shape 'square' | 'rounded' | 'pill' | 'rounded' |
type | BtnType | any 'button' | 'submit' | 'reset' | 'button' |
loading | boolean | false |
disabled | boolean | null | false |
class | string | '' |
Snippets
| Name | Type | Default |
|---|---|---|
children | Snippet | — |
Source
Show the IconButton.svelte source
<script lang="ts">
import type { Snippet } from 'svelte';
import type { HTMLButtonAttributes } from 'svelte/elements';
import type { Variant, Size, Shape, BtnType } from '../../types';
interface Props extends Omit<HTMLButtonAttributes, 'type'> {
label: string;
variant?: Variant;
size?: Size;
shape?: Shape;
type?: BtnType | any;
loading?: boolean;
disabled?: boolean | null;
class?: string;
children?: Snippet;
}
let {
label,
variant = 'secondary',
size = 'md',
shape = 'rounded',
type = 'button',
loading = false,
disabled = false,
class: className = '',
children,
...restProps
}: Props = $props();
const inert = $derived(disabled || loading);
</script>
<button
{type}
class="f-icon-btn {className}"
data-variant={variant}
data-size={size}
data-shape={shape}
data-state={loading ? 'loading' : undefined}
aria-label={label}
title={label}
disabled={inert}
aria-busy={loading || undefined}
{...restProps}
>
{#if loading}
<span class="f-btn-spinner" aria-hidden="true"></span>
{:else if children}
{@render children()}
{/if}
</button>
<style>
@layer fractalsvelte-ui.components {
.f-icon-btn {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0;
cursor: pointer;
user-select: none;
border: 1px solid transparent;
box-sizing: border-box;
flex-shrink: 0;
transition: background var(--f-duration-fast) var(--f-ease),
border-color var(--f-duration-fast) var(--f-ease),
color var(--f-duration-fast) var(--f-ease),
transform var(--f-duration-fast) var(--f-ease);
}
.f-icon-btn:focus-visible {
outline: 2px solid var(--f-ring);
outline-offset: 2px;
}
.f-icon-btn:active:not(:disabled) {
transform: scale(0.95);
}
.f-icon-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
pointer-events: none;
}
/* Shapes */
.f-icon-btn[data-shape='rounded'] {
border-radius: var(--f-radius-md);
}
.f-icon-btn[data-shape='pill'] {
border-radius: var(--f-radius-full);
}
.f-icon-btn[data-shape='square'] {
border-radius: 0;
}
/* Sizes */
.f-icon-btn[data-size='xs'] {
width: 24px;
height: 24px;
font-size: var(--f-text-xs);
}
.f-icon-btn[data-size='sm'] {
width: 32px;
height: 32px;
font-size: var(--f-text-sm);
}
.f-icon-btn[data-size='md'] {
width: 40px;
height: 40px;
font-size: var(--f-text-base);
}
.f-icon-btn[data-size='lg'] {
width: 48px;
height: 48px;
font-size: var(--f-text-lg);
}
.f-icon-btn[data-size='xl'] {
width: 56px;
height: 56px;
font-size: var(--f-text-xl);
}
/* Variants */
.f-icon-btn[data-variant='primary'] {
background: var(--f-primary);
color: var(--f-primary-text);
border-color: var(--f-primary);
}
.f-icon-btn[data-variant='primary']:hover:not(:disabled) {
background: var(--f-primary-hover);
}
.f-icon-btn[data-variant='secondary'] {
background: var(--f-surface);
color: var(--f-text);
border-color: var(--f-border);
box-shadow: var(--f-shadow-xs);
}
.f-icon-btn[data-variant='secondary']:hover:not(:disabled) {
background: var(--f-hover);
border-color: var(--f-border-strong);
}
.f-icon-btn[data-variant='soft'] {
background: var(--f-primary-subtle);
color: var(--f-primary);
}
.f-icon-btn[data-variant='soft']:hover:not(:disabled) {
filter: brightness(0.95);
}
.f-icon-btn[data-variant='outline'] {
background: transparent;
color: var(--f-text);
border-color: var(--f-border-strong);
}
.f-icon-btn[data-variant='outline']:hover:not(:disabled) {
background: var(--f-hover);
color: var(--f-primary);
border-color: var(--f-primary);
}
.f-icon-btn[data-variant='ghost'] {
background: transparent;
color: var(--f-text-muted);
}
.f-icon-btn[data-variant='ghost']:hover:not(:disabled) {
background: var(--f-hover);
color: var(--f-text);
}
.f-icon-btn[data-variant='destructive'] {
background: var(--f-danger);
color: #ffffff;
border-color: var(--f-danger);
}
.f-icon-btn[data-variant='surface'] {
background: var(--f-raised);
color: var(--f-text);
border-color: var(--f-border-subtle);
}
.f-btn-spinner {
width: 14px;
height: 14px;
border: 2px solid currentColor;
border-top-color: transparent;
border-radius: 50%;
animation: f-icon-spin 0.6s linear infinite;
}
@keyframes f-icon-spin {
to {
transform: rotate(360deg);
}
}
}
</style>Related components
LinkButton A semantic navigation anchor styled with button dimensions and hover aesthetics.SlideUnlock A swipe-to-unlock gesture slider with draggable spring thumb, progress track fill, and success state.SplitButton A dual-action control pairing a primary button trigger with a secondary dropdown chevron button.Button An interactive trigger for user actions with support for multiple visual variants, sizes, shapes, and an…ButtonGroup A clustered group container that attaches adjacent buttons seamlessly with merged borders or separates them…CloseButton A standardized dismissal trigger for dialogs, drawers, toasts, and alerts.