IconButton

An icon-only button that requires a name and shows it on hover.

<script setup lang="ts">
  import { Bookmark, Download, Pencil, Share2, Trash2 } from '@lucide/vue'
  import { IconButton, Inline } from '@hina-ui/vue'
</script>

<template>
  <Inline>
    <IconButton label="Edit">
      <Pencil />
    </IconButton>
    <IconButton label="Bookmark">
      <Bookmark />
    </IconButton>
    <IconButton label="Share">
      <Share2 />
    </IconButton>
    <IconButton label="Download">
      <Download />
    </IconButton>
    <IconButton label="Delete" tone="danger">
      <Trash2 />
    </IconButton>
  </Inline>
</template>
vue

Usage

import { IconButton } from '@hina-ui/vue'
ts

label is required and serves as both the accessible name and the tooltip text. The default slot holds the icon.

<script setup lang="ts">
  import { Search } from '@lucide/vue'
  import { IconButton } from '@hina-ui/vue'
</script>

<template>
  <IconButton label="Search">
    <Search />
  </IconButton>
</template>
vue

Toolbars, the end of table rows and the top corner of cards are where space is tight and this component fits; actions with visible text still use Button.

Examples

Variants and tones

Variants and tones are identical to Button, but the defaults differ. IconButton defaults to ghost and neutral, because it usually appears in dense interfaces where it should not stand out.

<script setup lang="ts">
  import { Settings, Trash2 } from '@lucide/vue'
  import { IconButton, Inline } from '@hina-ui/vue'
</script>

<template>
  <Inline>
    <IconButton label="Settings" variant="solid" tone="accent">
      <Settings />
    </IconButton>
    <IconButton label="Settings" variant="soft">
      <Settings />
    </IconButton>
    <IconButton label="Settings" variant="outline">
      <Settings />
    </IconButton>
    <IconButton label="Settings">
      <Settings />
    </IconButton>
    <IconButton label="Delete" variant="soft" tone="danger">
      <Trash2 />
    </IconButton>
  </Inline>
</template>
vue

Sizes

Three sizes. The button is square, with a side equal to the height of a Button at the same step.

<script setup lang="ts">
  import { Star } from '@lucide/vue'
  import { IconButton, Inline } from '@hina-ui/vue'
</script>

<template>
  <Inline align="center">
    <IconButton label="Small" size="sm" variant="outline">
      <Star />
    </IconButton>
    <IconButton label="Medium" size="md" variant="outline">
      <Star />
    </IconButton>
    <IconButton label="Large" size="lg" variant="outline">
      <Star />
    </IconButton>
  </Inline>
</template>
vue

States

With loading the button shows a spinner and refuses clicks; with disabled it is unavailable. The size stays the same while loading.

<script setup lang="ts">
  import { ref } from 'vue'
  import { RotateCw, Trash2 } from '@lucide/vue'
  import { IconButton, Inline } from '@hina-ui/vue'

  const refreshing = ref(false)

  function refresh() {
    refreshing.value = true
    setTimeout(() => (refreshing.value = false), 2000)
  }
</script>

<template>
  <Inline>
    <IconButton label="Refresh" variant="outline" :loading="refreshing" @click="refresh">
      <RotateCw />
    </IconButton>
    <IconButton label="Delete" variant="outline" tone="danger" disabled>
      <Trash2 />
    </IconButton>
  </Inline>
</template>
vue

Tooltip

side sets where the tooltip appears, defaulting to above. Setting tooltip to false keeps the accessible name without showing a tooltip.

<script setup lang="ts">
  import { Copy, Download, Heart, Search } from '@lucide/vue'
  import { IconButton, Inline } from '@hina-ui/vue'
</script>

<template>
  <Inline>
    <IconButton label="Tooltip above" variant="outline">
      <Search />
    </IconButton>
    <IconButton label="Tooltip on the right" side="right" variant="outline">
      <Copy />
    </IconButton>
    <IconButton label="Tooltip below" side="bottom" variant="outline">
      <Download />
    </IconButton>
    <IconButton label="No tooltip" :tooltip="false" variant="outline">
      <Heart />
    </IconButton>
  </Inline>
</template>
vue

The tooltip relies on a TooltipProvider at the application root. Without one the button still works, only without the tooltip, and aria-label is unaffected.

Accessibility

  • label is required and becomes the aria-label that screen readers announce.
  • Icons are invisible to screen readers, so never drop label and rely on the icon alone to carry meaning.
  • A loading button carries aria-busy, which screen readers announce as busy.

API

Props

Prop
Type
Default
Description
label
string
—
Required. Accessible name, also the tooltip text
tooltip
boolean
true
Whether to show a tooltip on hover
side
'top' | 'right' | 'bottom' | 'left'
'top'
Where the tooltip appears
as
string | Component
'button'
Element or component to render
asChild
boolean
false
Render nothing, merging into the single child
variant
'solid' | 'soft' | 'outline' | 'ghost' | 'link'
'ghost'
Visual style
tone
'accent' | 'neutral' | 'danger'
'neutral'
Semantic tone
size
'xs' | 'sm' | 'md' | 'lg'
'md'
Size
type
'button' | 'submit' | 'reset'
'button'
Native button type
pill
boolean
false
Render as a circle
loading
boolean
false
Whether it is loading
disabled
boolean
false
Whether it is disabled
class
string
—
Classes appended to the root

Slots

Slot
Description
default
The icon, replaced by a spinner while loading