IconButton
An icon-only square button with a required aria-label and 3 sizes.
'use client';
import { IconButton } from '@akhatmirzo/ui';
import { Heart, Pencil, Plus, Share2, Trash2 } from 'lucide-react';
export default function IconButtonVariants() {
return (
<div className="flex flex-wrap items-center gap-3">
<IconButton variant="primary" aria-label="Qo'shish">
<Plus />
</IconButton>
<IconButton aria-label="Sevimlilarga qo'shish">
<Heart />
</IconButton>
<IconButton variant="outline" aria-label="Tahrirlash">
<Pencil />
</IconButton>
<IconButton variant="ghost" aria-label="Ulashish">
<Share2 />
</IconButton>
<IconButton variant="danger" aria-label="O'chirish">
<Trash2 />
</IconButton>
</div>
);
}
Import
import { IconButton } from '@akhatmirzo/ui';When to use
- When space is tight and the icon is universally understood — close (✕), edit, delete,
toolbar actions. If the meaning isn't obvious, use a
Buttonwith text. aria-labelis required. TypeScript won't compile without it. Screen readers can't see the icon and read only this label, so phrase it as an action: "Close", "Edit".- Need a visible hint? Wrap the button in a
Tooltip. - In toolbars and table rows, the
ghostvariant keeps things light.
Variants
The same variants as Button, minus link: primary, soft (default), outline, ghost and
danger. See the Button page for when to use each.
Sizes
Square: 32, 40 and 48px — the same heights as Button and Input. The icon scales
automatically to 16 / 18 / 20px. The sm size has an invisible 40px hit area.
'use client';
import { IconButton } from '@akhatmirzo/ui';
import { X } from 'lucide-react';
export default function IconButtonSizes() {
return (
<div className="flex flex-wrap items-center gap-3">
<IconButton variant="outline" size="sm" aria-label="Yopish">
<X />
</IconButton>
<IconButton variant="outline" size="md" aria-label="Yopish">
<X />
</IconButton>
<IconButton variant="outline" size="lg" aria-label="Yopish">
<X />
</IconButton>
</div>
);
}
States
While loading, a spinner replaces the icon, the button keeps focus and clicks are ignored. Try
clicking the first button.
'use client';
import { IconButton } from '@akhatmirzo/ui';
import { Download, Trash2 } from 'lucide-react';
import { useState } from 'react';
export default function IconButtonStates() {
const [loading, setLoading] = useState(false);
const download = () => {
setLoading(true);
setTimeout(() => setLoading(false), 1500);
};
return (
<div className="flex flex-wrap items-center gap-3">
<IconButton
variant="primary"
loading={loading}
onClick={download}
aria-label="Hisobotni yuklab olish"
labels={{ loading: 'Yuklab olinmoqda' }}
>
<Download />
</IconButton>
<IconButton variant="danger" disabled aria-label="O'chirish">
<Trash2 />
</IconButton>
</div>
);
}
Customization
Change shape and layout with className — for example, rounded-full for a floating action
button. The example below builds a toolbar from ghost buttons.
'use client';
import { IconButton } from '@akhatmirzo/ui';
import { Bold, Italic, Plus, Underline } from 'lucide-react';
export default function IconButtonToolbar() {
return (
<div className="flex flex-wrap items-center gap-6">
<div
role="toolbar"
aria-label="Matn formati"
className="flex items-center gap-1 rounded-lg border border-border bg-surface p-1"
>
<IconButton variant="ghost" size="sm" aria-label="Qalin">
<Bold />
</IconButton>
<IconButton variant="ghost" size="sm" aria-label="Kursiv">
<Italic />
</IconButton>
<IconButton variant="ghost" size="sm" aria-label="Tagiga chizilgan">
<Underline />
</IconButton>
</div>
<IconButton variant="primary" size="lg" className="rounded-full" aria-label="Yangi buyurtma">
<Plus />
</IconButton>
</div>
);
}
As a link (asChild)
IconButton accepts asChild too: you get the button styles on your own <a> (or Next.js
<Link>).
'use client';
import { IconButton } from '@akhatmirzo/ui';
import { Settings } from 'lucide-react';
export default function IconButtonAsChild() {
return (
<IconButton asChild variant="outline" aria-label="Sozlamalar">
<a href="#props">
<Settings />
</a>
</IconButton>
);
}
Props
IconButton is a thin wrapper around Button. It accepts loading, disabled, asChild,
labels and every other <button> attribute; leftIcon, rightIcon and fullWidth are not
available here.
| Prop | Type | Default | Description |
|---|---|---|---|
aria-labelrequired | string | — | The name screen readers announce |
variant | 'primary' | 'soft' | 'outline' | 'ghost' | 'danger' | 'soft' | Look and visual weight |
size | 'sm' | 'md' | 'lg' | 'md' | Square size: 32 / 40 / 48px |
loading | boolean | false | Shows a spinner instead of the icon, blocks clicks, sets aria-busy |
disabled | boolean | false | Disables the button |
asChild | boolean | false | Renders its child instead of its own element (e.g. <a>) |
labels | { loading?: string } | { loading: 'Yuklanmoqda' } | Loading text for screen readers |
children | ReactNode | — | The icon (SVG). Don't put text here — that's what Button is for |
Data attributes are the same as Button's: data-variant, data-size, data-disabled and
data-loading.
Accessibility
- A name is required.
aria-labelis enforced by the type — an unnamed button won't compile. - Native
<button type="button">— activates withEnterandSpaceand never submits a form by accident. - The icon is hidden from screen readers (
aria-hidden); the only thing announced is thearia-label. - Loading. Sets
aria-busy="true", and a hiddenaria-liveregion announces thelabels.loadingtext. - Small size.
size="sm"looks 32px but has a 40px hit area.