Skip to content

Icon button

Offer a compact action, such as Search or Close, with an icon and an accessible name for screen readers.

  • INV-0042

    Acme Corp

  • INV-0043

    Birch & Co

  • INV-0044

    Cedar Ltd

<script setup lang="ts">import { Columns3, Copy, Pencil, Plus, SlidersHorizontal, Trash2 } from '@lucide/vue'import { UiStack, UiIconButton, UiButton, UiCard, UiList, UiListItem } from 'grani' const INVOICES = [  { id: 'INV-0042', client: 'Acme Corp' },  { id: 'INV-0043', client: 'Birch & Co' },  { id: 'INV-0044', client: 'Cedar Ltd' },]</script> <template>  <UiStack gap="tight" class="w-full max-w-md">    <div class="flex items-center justify-end gap-2">      <UiIconButton :icon="SlidersHorizontal" ariaLabel="Filters" />      <UiIconButton :icon="Columns3" ariaLabel="Columns" />      <UiButton><Plus aria-hidden="true" />New invoice</UiButton>    </div>    <UiCard :padded="false">      <UiList>        <UiListItem          v-for="row in INVOICES"          :key="row.id"          :title="row.id"          :description="row.client"        >          <template #actions>            <UiIconButton :icon="Pencil" variant="ghost" :ariaLabel="`Edit ${row.id}`" />            <UiIconButton :icon="Copy" variant="ghost" :ariaLabel="`Duplicate ${row.id}`" />            <UiIconButton              :icon="Trash2"              variant="ghost"              tone="danger"              :ariaLabel="`Delete ${row.id}`"            />          </template>        </UiListItem>      </UiList>    </UiCard>  </UiStack></template>

Also called icon-only button, glyph button.

Usage

<UiIconButton :aria-label="ariaLabel">…</UiIconButton>

With GraniResolver in the Vite config, a template needs no import: set it up once.

API

Name · typeDefaultDescription
State
loadingboolean= falsefalseA request is running: spinner instead of the glyph, aria-busy, no clicks; focus stays, as in UiButton.
Behavior
typebutton | submit | reset= "button""button"Native type; submit sends the form the button sits in.
disabledReasonstringWhy it cannot be pressed now, while disabled, as UiButton's: it stays in the Tab order, refuses the press, and says the reason in a tooltip and as its description.
pressedbooleanA toggle (pin, bookmark), as UiButton's pressed: aria-pressed, and on, the brand frame and plate. Unset, it is no toggle. A primary square is never one.
Look
iconComponentA Lucide icon component, e.g. Pencil from @lucide/vue.
variantoutline | primary | ghost= "outline""outline"Presentation: primary is the brand fill (the arrow of a split button), outline a white square with a border, ghost only the glyph until hovered.
tonebrand | neutral | danger= "neutral""neutral"Meaning of the glyph: gray, brand, or a dangerous action. pressed on overrides it with the brand frame.
sizexs | sm | mdStep of the control ladder: xs 28px for a dismiss in a corner, sm 36px, md 44px for forms. Unset, the app's size (createGrani({ size })), sm by default.
Form and access
ariaLabel*stringThe accessible name, for a screen reader; no hint shows it, so wrap the button in UiTooltip where a sighted user needs it. Required: the button has no visible text.
disabledbooleanNatively disabled: out of the Tab order, grayed; with disabledReason, focusable and saying why.

Attributes are placed by the component itself: its description says where.

When to use another component

When you needTake
TextButton
Copying a valueCopy button
A choice among optionsRadio group segmented
A primary square as a toggleNever; it is the arrow of a split button in Button group