ButtonGroup

Joins several buttons into one unit that shares borders and corners.

<script setup lang="ts">
  import { AlignCenter, AlignLeft, AlignRight } from '@lucide/vue'
  import { ButtonGroup, IconButton } from '@hina-ui/vue'
</script>

<template>
  <ButtonGroup label="Alignment">
    <IconButton label="Align left" variant="outline">
      <AlignLeft />
    </IconButton>
    <IconButton label="Align center" variant="outline">
      <AlignCenter />
    </IconButton>
    <IconButton label="Align right" variant="outline">
      <AlignRight />
    </IconButton>
  </ButtonGroup>
</template>
vue

Usage

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

Put the buttons straight into the group; joining them is the container’s job. The outer corners at each end are kept, the corners where two buttons meet are dropped, and adjacent borders overlap into one rule rather than sitting side by side.

<script setup lang="ts">
  import { Button, ButtonGroup } from '@hina-ui/vue'
</script>

<template>
  <ButtonGroup label="View">
    <Button variant="outline" tone="neutral">List</Button>
    <Button variant="outline" tone="neutral">Grid</Button>
    <Button variant="outline" tone="neutral">Timeline</Button>
  </ButtonGroup>
</template>
vue

Buttons in a group should share one variant and one tone. Only actions of equal standing belong together; actions of different weight should be placed apart.

Examples

Variants

Variant and tone are set on each button; the container does not assign them.

<script setup lang="ts">
  import { Button, ButtonGroup, Stack } from '@hina-ui/vue'
</script>

<template>
  <Stack align="center">
    <ButtonGroup label="Solid">
      <Button variant="solid" tone="accent">Back</Button>
      <Button variant="solid" tone="accent">Save</Button>
      <Button variant="solid" tone="accent">Next</Button>
    </ButtonGroup>

    <ButtonGroup label="Soft">
      <Button variant="soft" tone="accent">Back</Button>
      <Button variant="soft" tone="accent">Save</Button>
      <Button variant="soft" tone="accent">Next</Button>
    </ButtonGroup>

    <ButtonGroup label="Outline">
      <Button variant="outline" tone="neutral">Back</Button>
      <Button variant="outline" tone="neutral">Save</Button>
      <Button variant="outline" tone="neutral">Next</Button>
    </ButtonGroup>
  </Stack>
</template>
vue

Sizes

Size is likewise set on the buttons. A group must use one size throughout, otherwise the heights of neighbouring buttons will not line up.

<script setup lang="ts">
  import { Button, ButtonGroup, Stack } from '@hina-ui/vue'
</script>

<template>
  <Stack align="center">
    <ButtonGroup label="Small">
      <Button size="sm" variant="outline" tone="neutral">Day</Button>
      <Button size="sm" variant="outline" tone="neutral">Week</Button>
      <Button size="sm" variant="outline" tone="neutral">Month</Button>
    </ButtonGroup>

    <ButtonGroup label="Medium">
      <Button variant="outline" tone="neutral">Day</Button>
      <Button variant="outline" tone="neutral">Week</Button>
      <Button variant="outline" tone="neutral">Month</Button>
    </ButtonGroup>

    <ButtonGroup label="Large">
      <Button size="lg" variant="outline" tone="neutral">Day</Button>
      <Button size="lg" variant="outline" tone="neutral">Week</Button>
      <Button size="lg" variant="outline" tone="neutral">Month</Button>
    </ButtonGroup>
  </Stack>
</template>
vue

Vertical

With orientation="vertical" the group joins vertically, the shared edges move to the top and bottom, and the width of the group is set by its widest item.

<script setup lang="ts">
  import { Copy, Scissors, Trash2 } from '@lucide/vue'
  import { Button, ButtonGroup } from '@hina-ui/vue'
</script>

<template>
  <ButtonGroup label="File actions" orientation="vertical" divider>
    <Button variant="soft" tone="neutral">
      <template #icon>
        <Copy />
      </template>
      Copy
    </Button>
    <Button variant="soft" tone="neutral">
      <template #icon>
        <Scissors />
      </template>
      Cut
    </Button>
    <Button variant="soft" tone="neutral">
      <template #icon>
        <Trash2 />
      </template>
      Delete
    </Button>
  </ButtonGroup>
</template>
vue

Full width

With block the group fills the width of its container and the buttons divide the remaining space equally. This form suits dialog footers and mobile layouts.

<script setup lang="ts">
  import { Button, ButtonGroup } from '@hina-ui/vue'
</script>

<template>
  <ButtonGroup label="Vote" block>
    <Button variant="outline" tone="neutral">For</Button>
    <Button variant="outline" tone="neutral">Abstain</Button>
    <Button variant="outline" tone="neutral">Against</Button>
  </ButtonGroup>
</template>
vue

Dividers

When buttons share a background colour, the boundary between them is not obvious. divider adds a thin rule between them, shorter than the full edge, coloured from the current text colour.

<script setup lang="ts">
  import { Button, ButtonGroup, Stack } from '@hina-ui/vue'
</script>

<template>
  <Stack align="center">
    <ButtonGroup label="Without divider">
      <Button variant="soft" tone="neutral">Copy</Button>
      <Button variant="soft" tone="neutral">Cut</Button>
      <Button variant="soft" tone="neutral">Paste</Button>
    </ButtonGroup>

    <ButtonGroup label="With divider" divider>
      <Button variant="soft" tone="neutral">Copy</Button>
      <Button variant="soft" tone="neutral">Cut</Button>
      <Button variant="soft" tone="neutral">Paste</Button>
    </ButtonGroup>
  </Stack>
</template>
vue

Mixing in icon buttons

Button and IconButton can be joined in one group; a common pattern is a primary action followed by a dropdown trigger.

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

<template>
  <ButtonGroup label="Publish">
    <Button>Publish now</Button>
    <IconButton label="More publish options" variant="solid" tone="accent">
      <ChevronDown />
    </IconButton>
  </ButtonGroup>
</template>
vue

Unavailable

The container has no disabled property. To disable a whole group, set disabled on each button; a single button can also be disabled on its own.

<script setup lang="ts">
  import { Button, ButtonGroup, Stack } from '@hina-ui/vue'
</script>

<template>
  <Stack align="center">
    <ButtonGroup label="Whole group disabled">
      <Button variant="outline" tone="neutral" disabled>Bold</Button>
      <Button variant="outline" tone="neutral" disabled>Italic</Button>
      <Button variant="outline" tone="neutral" disabled>Underline</Button>
    </ButtonGroup>

    <ButtonGroup label="One disabled">
      <Button variant="outline" tone="neutral" disabled>Previous</Button>
      <Button variant="outline" tone="neutral">Refresh</Button>
      <Button variant="outline" tone="neutral">Next</Button>
    </ButtonGroup>
  </Stack>
</template>
vue

Custom styles

class is appended to the container. To change the corners at each end, set them from the container with child selectors rather than writing them on every button.

<script setup lang="ts">
  import { Button, ButtonGroup } from '@hina-ui/vue'
</script>

<template>
  <ButtonGroup
    label="Sort"
    class="[&>*:first-child]:rounded-s-full [&>*:last-child]:rounded-e-full"
  >
    <Button variant="soft" tone="neutral" class="px-6">Newest</Button>
    <Button variant="soft" tone="neutral" class="px-6">Popular</Button>
    <Button variant="soft" tone="neutral" class="px-6">Rating</Button>
  </ButtonGroup>
</template>
vue

Behaviour

  • Buttons in a group no longer scale on press.
  • The focused button is raised above the others, so its focus ring is never covered by a neighbour.
  • The container is a role="group" and label becomes the accessible name of the whole group. A vertical group also carries aria-orientation="vertical".

API

Props

Prop
Type
Default
Description
label
string
—
Accessible name of the group
orientation
'horizontal' | 'vertical'
'horizontal'
Direction in which buttons are joined
block
boolean
false
Fill the container and divide space evenly
divider
boolean
false
Show a rule between adjacent buttons
class
string
—
Classes appended to the root

Slots

Slot
Description
default
The buttons in the group