Keyboard shortcut: Ctrl + K
Get started

Icon button

A square button carrying an icon and no label. It is VButton underneath, with two defaults of its own and a required name, since the picture is all a screen reader would otherwise get.

Usage

vue
<script setup lang="ts">
import { VIconButton } from 'vectis-ui'
import { search } from 'vectis-ui/icons'
</script>

<template>
  <VIconButton label="Search" :icon="search" />
</template>

Examples

Variants and tones

The same four variants and three tones as VButton, with two defaults of its own: ghost rather than solid, and neutral rather than accent.

vue
<script setup lang="ts">
import { VIconButton } from 'vectis-ui'
import { notifications } from 'vectis-ui/icons'

const variants = ['solid', 'soft', 'outline', 'ghost'] as const
const tones = ['accent', 'neutral', 'danger'] as const
</script>

<template>
  <div v-for="tone in tones" :key="tone" class="row">
    <VIconButton
      v-for="variant in variants"
      :key="variant"
      :icon="notifications"
      :variant="variant"
      :tone="tone"
      :label="`Notifications, ${variant} ${tone}`"
    />
  </div>
</template>

<style scoped>
.row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--vectis-space-3);
}
</style>

Elevated

elevated applies the shadow scale to whichever variant is in use. Ghost and outline also gain a raised surface.

vue
<script setup lang="ts">
import { VIconButton } from 'vectis-ui'
import { notifications } from 'vectis-ui/icons'

const variants = ['solid', 'soft', 'outline', 'ghost'] as const
</script>

<template>
  <div class="row">
    <VIconButton
      v-for="variant in variants"
      :key="variant"
      :icon="notifications"
      :variant="variant"
      :label="`Notifications, ${variant}`"
    />
  </div>

  <div class="row">
    <VIconButton
      v-for="variant in variants"
      :key="variant"
      :icon="notifications"
      :variant="variant"
      elevated
      :label="`Notifications, ${variant} raised`"
    />
  </div>
</template>

<style scoped>
.row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--vectis-space-3);
}
</style>

Sizes

size sets the box, square at each of the five steps from 24 to 56 pixels. compact takes 4px off both sides, so the box stays square.

vue
<script setup lang="ts">
import { VIconButton } from 'vectis-ui'
import { search } from 'vectis-ui/icons'

const sizes = ['xs', 'sm', 'md', 'lg', 'xl'] as const
</script>

<template>
  <div class="row">
    <VIconButton
      v-for="size in sizes"
      :key="size"
      :icon="search"
      :size="size"
      variant="outline"
      :label="`Search, ${size}`"
    />
  </div>

  <!-- Compact takes 4px off both sides, so the box stays square and the icon inside it
       keeps the size its step gives it. -->
  <div class="row">
    <VIconButton
      v-for="size in sizes"
      :key="size"
      :icon="search"
      :size="size"
      compact
      variant="outline"
      :label="`Search, ${size} compact`"
    />
  </div>
</template>

<style scoped>
.row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--vectis-space-3);
}
</style>

Shapes

shape chooses the silhouette, square or circular. Inside a VButtonGroup the row's own corner rules win and a circular segment keeps square seams.

vue
<script setup lang="ts">
import { VButtonGroup, VIconButton } from 'vectis-ui'
import {
  arrow_downward as arrowDownward,
  arrow_upward as arrowUpward,
  close,
  swap_vert as swapVert,
} from 'vectis-ui/icons'

const sizes = ['sm', 'md', 'lg'] as const
</script>

<template>
  <div class="stack">
    <div class="row">
      <VIconButton
        v-for="size in sizes"
        :key="size"
        :icon="close"
        :size="size"
        variant="outline"
        :label="`Close, ${size}`"
      />
      <VIconButton
        v-for="size in sizes"
        :key="`${size}-circular`"
        :icon="close"
        :size="size"
        shape="circular"
        variant="outline"
        :label="`Close, circular ${size}`"
      />
    </div>

    <!-- Inside a group the corner rules are the row's, and they win: a circular segment
         keeps square seams so the row still reads as one object. Intended, and the
         reason a shape is worth choosing before grouping rather than after. -->
    <VButtonGroup variant="outline">
      <VIconButton :icon="arrowUpward" shape="circular" label="Sort ascending" />
      <VIconButton :icon="swapVert" shape="circular" label="Unsorted" />
      <VIconButton :icon="arrowDownward" shape="circular" label="Sort descending" />
    </VButtonGroup>
  </div>
</template>

<style scoped>
.stack {
  display: flex;
  flex-direction: column;
  gap: var(--vectis-space-5);
  align-items: start;
}
.row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--vectis-space-3);
}
</style>

Icons

icon takes any icon value and iconFilled asks for its filled form. The default slot is the way to an icon the prop cannot express, and stays decorative: the button is named by its label.

vue
<script setup lang="ts">
import { VIcon, VIconButton } from 'vectis-ui'
import { ICON_VIEW_BOX, code, notifications, search } from 'vectis-ui/icons'

import firefoxLogo from '~/assets/img/firefox-browser-svg.svg'
</script>

<template>
  <!-- `icon` takes everything an icon prop in the library takes: one of its own icons,
       a bare name for your resolver, or an explicit render. -->
  <VIconButton :icon="search" variant="outline" label="Search" />
  <VIconButton icon="translate" variant="outline" label="Translate" />
  <VIconButton :icon="{ path: code.paths[0] }" variant="outline" label="View the source" />
  <VIconButton :icon="{ src: firefoxLogo }" variant="outline" label="Open in Firefox" />

  <!-- Filled marks a state, here a notification that has arrived. -->
  <VIconButton :icon="notifications" icon-filled variant="outline" label="3 new notifications" />

  <!-- The slot is the way to an icon the prop cannot express: a VIcon you configure
       yourself, or an inline SVG. The button is already named by its label, so whatever
       goes in here stays decorative. -->
  <VIconButton variant="outline" label="Notifications, larger glyph">
    <VIcon :name="notifications" :size="28" />
  </VIconButton>
  <VIconButton variant="outline" label="Add">
    <svg :viewBox="ICON_VIEW_BOX" fill="none" stroke="currentColor" stroke-width="80">
      <path d="M480-800v640M160-480h640" stroke-linecap="round" />
    </svg>
  </VIconButton>
</template>

href renders the button as an anchor, on the terms of VButton. A disabled link keeps its place and loses its destination.

vue
<script setup lang="ts">
import { VIconButton } from 'vectis-ui'
import { arrow_right_alt as arrowRightAlt, code } from 'vectis-ui/icons'
</script>

<template>
  <!-- `href` renders an <a> instead of a <button>. -->
  <VIconButton href="#usage" :icon="arrowRightAlt" variant="outline" label="Back to usage" />

  <VIconButton
    href="https://github.com"
    target="_blank"
    rel="noreferrer"
    :icon="code"
    variant="outline"
    label="View the source, opens in a new tab"
  />

  <!-- A disabled link keeps its place and loses its destination: the href is removed
       and the clicks are dropped, since nothing in HTML disables an anchor. -->
  <VIconButton href="#usage" :icon="arrowRightAlt" variant="outline" disabled label="Unavailable" />
</template>

States

disabled greys the button out through the colour tokens. loading puts a spinner in the icon's own box and disables the button while it turns.

vue
<script setup lang="ts">
import { VIconButton } from 'vectis-ui'
import { notifications } from 'vectis-ui/icons'

const variants = ['solid', 'soft', 'outline', 'ghost'] as const
</script>

<template>
  <div class="row">
    <VIconButton
      v-for="variant in variants"
      :key="variant"
      :icon="notifications"
      :variant="variant"
      disabled
      :label="`Notifications, ${variant} disabled`"
    />
  </div>

  <!-- Loading puts a spinner where the icon was and disables the button while it turns,
       so the same action cannot be asked for twice. The spinner takes the icon's box,
       which is what keeps the square from changing size. -->
  <div class="row">
    <VIconButton :icon="notifications" loading label="Refreshing" />
    <VIconButton :icon="notifications" variant="outline" loading label="Checking" />
    <VIconButton :icon="notifications" variant="ghost" loading label="Loading" />
  </div>
</template>

<style scoped>
.row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--vectis-space-3);
}
</style>

API

Props

PropTypeDefault
labelstringnone
What the button does, in words. It becomes the aria-label and is the only thing a screen reader has to go on, so it names the action, "Close", "Next month", rather than the picture.
variantButtonVariant'solid' | 'outline' | 'ghost' | 'soft''ghost'
How much visual weight the button carries, on the VButton scale. Inside a VButtonGroup the group decides it.
toneButtonTone'accent' | 'neutral' | 'danger''neutral'
What the action means, in colour. An icon-only button is usually chrome, which is why it starts neutral where VButton starts accent. Left out inside a VButtonGroup it takes the group's tone.
elevatedbooleanfalse
Raises the button with a shadow, and a raised surface on ghost and outline.
sizeButtonSize'xs' | 'sm' | 'md' | 'lg' | 'xl''md'
The size of the square, taken from the scale shared by every control.
compactbooleanfalse
Takes 4px off both sides of the square, which stays square.
shapeIconButtonShape'square' | 'circular''square'
The silhouette: a square carrying the corner radius every control shares, or a circle. The box itself is square either way, only the corners change.
hrefstringnone
Turns the button into an <a> pointing at this address. A disabled or loading link becomes inert: the address is dropped, so it can be neither focused nor followed.
typeButtonHTMLAttributes['type']'button'
The native type of the button. It is ignored as soon as href makes it a link.
disabledbooleanfalse
Makes the button unusable, greyed out through the colour tokens.
loadingbooleanfalse
Replaces the icon with a spinner and disables the button while it turns.
iconIconSourcenone
The icon to show. The default slot is the way to supply one this prop cannot express.
iconFilledbooleanfalse
Renders the icon in its filled form, the font's FILL axis.

Slots

SlotType
default{}
The icon, when the icon prop cannot express it: a VIcon, or an inline SVG marked aria-hidden, the button being already named by its label.

Types

The types the tables above name, written as the library declares them. The ones carrying export can be imported from vectis-ui to type your own code; the others are the shape of what a slot hands out.

export interface BuiltinIcon {
  name: string
  paths: readonly [string] | readonly [string, string]
}
export type IconRender =
  | { path: string; viewBox?: string }
  | { component: Component; props?: Record<string, unknown> }
  | { src: string }
  | { text: string; class?: string }
  | { class: string }
export type IconSource = string | BuiltinIcon | IconRender