ShardsUI is in beta. APIs may change before 1.0.

Toolbar

A row of grouped controls.

<script lang="ts">
  import { Toolbar } from '@shardsui/svelte/toolbar'
  import { ToggleGroup } from '@shardsui/svelte/toggle-group'
  import { Toggle } from '@shardsui/svelte/toggle'
  import { Select } from '@shardsui/svelte/select'

  const views = [
    { value: 'grid', label: 'Grid view', text: 'Grid' },
    { value: 'list', label: 'List view', text: 'List' }
  ]

  const zoomControls = [
    { label: 'Zoom out', text: '−' },
    { label: 'Zoom in', text: '+' }
  ]

  const sorts = ['Newest', 'Oldest']
</script>

<Toolbar.Root
  class="flex w-full max-w-150 items-center gap-px rounded-md border border-gray-200 bg-gray-50 p-px"
>
  <ToggleGroup class="flex gap-px" aria-label="View">
    {#each views as view (view.value)}
      <Toggle
        aria-label={view.label}
        value={view.value}
        class="font-inherit flex h-8 min-w-8 items-center justify-center gap-2 rounded-xs px-3 text-sm font-normal text-gray-600 select-none hover:bg-gray-100 focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-gray-950 active:bg-gray-200 data-pressed:bg-gray-100 data-pressed:text-gray-900"
      >
        {view.text}
      </Toggle>
    {/each}
  </ToggleGroup>
  <Toolbar.Separator class="m-1 h-4 w-px bg-gray-300" />
  <Toolbar.Group class="flex gap-px" aria-label="Zoom">
    {#each zoomControls as control (control.label)}
      <Toolbar.Button
        class="font-inherit flex h-8 min-w-8 items-center justify-center gap-2 rounded-xs text-sm font-normal text-gray-600 select-none hover:bg-gray-100 focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-gray-950 active:bg-gray-200"
        aria-label={control.label}
      >
        {control.text}
      </Toolbar.Button>
    {/each}
  </Toolbar.Group>
  <Toolbar.Separator class="m-1 h-4 w-px bg-gray-300" />
  <Select.Root value="Newest">
    <Select.Trigger
      class="flex h-8 min-w-32 items-center justify-between gap-2 rounded-md pr-1 pl-2 text-sm font-normal text-gray-600 select-none hover:bg-gray-100 focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-gray-950 data-popup-open:bg-gray-100"
    >
      <Select.Value />
      <Select.Icon class="flex">
        {@render chevronGrabberIcon()}
      </Select.Icon>
    </Select.Trigger>
    <Select.Portal>
      <Select.Positioner class="outline-hidden select-none" sideOffset={8}>
        <Select.Popup
          class="group max-h-(--available-height) origin-(--transform-origin) overflow-y-auto rounded-md bg-gray-50 py-1 text-gray-900 shadow-lg outline-1 outline-gray-200 transition-[transform,scale,opacity] duration-100 ease-out data-ending-style:scale-95 data-ending-style:opacity-0 data-starting-style:scale-95 data-starting-style:opacity-0 data-[side=none]:data-ending-style:transition-none data-[side=none]:data-starting-style:scale-100 data-[side=none]:data-starting-style:opacity-100 data-[side=none]:data-starting-style:transition-none"
        >
          {#each sorts as sort (sort)}
            <Select.Item
              value={sort}
              class="grid min-w-(--anchor-width) grid-cols-[1rem_1fr] items-center gap-2 py-1.5 pr-4 pl-2.5 leading-4 outline-hidden select-none group-data-[side=none]:min-w-[calc(var(--anchor-width)+1rem)] group-data-[side=none]:pr-12 data-highlighted:relative data-highlighted:z-0 data-highlighted:text-gray-50 data-highlighted:before:absolute data-highlighted:before:inset-x-1 data-highlighted:before:inset-y-0 data-highlighted:before:z-[-1] data-highlighted:before:rounded-sm data-highlighted:before:bg-gray-900"
            >
              <Select.ItemIndicator class="col-start-1">
                {@render checkIcon()}
              </Select.ItemIndicator>
              <div class="col-start-2 text-sm">{sort}</div>
            </Select.Item>
          {/each}
        </Select.Popup>
      </Select.Positioner>
    </Select.Portal>
  </Select.Root>
  <Toolbar.Separator class="m-1 h-4 w-px bg-gray-300" />
  <Toolbar.Link
    class="mr-3.5 ml-auto flex-none self-center text-sm text-gray-500 no-underline hover:text-gray-900 focus-visible:rounded-xs focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-gray-950"
    href="/"
  >
    128 notes
  </Toolbar.Link>
</Toolbar.Root>

{#snippet chevronGrabberIcon()}
  <svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="size-4">
    <path
      d="M8 8.99981L11.1161 5.88369C11.6043 5.39554 12.3957 5.39554 12.8839 5.8837L16 8.99981"
      stroke="currentColor"
      stroke-width="1.5"
      stroke-linecap="round"
      stroke-linejoin="round"
    />
    <path
      d="M16 15L12.8839 18.1161C12.3957 18.6043 11.6043 18.6043 11.1161 18.1161L8 15"
      stroke="currentColor"
      stroke-width="1.5"
      stroke-linecap="round"
      stroke-linejoin="round"
    />
  </svg>
{/snippet}

{#snippet checkIcon()}
  <svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="size-4">
    <path
      d="M6 14.15L10.0321 18L18 7"
      stroke="currentColor"
      stroke-width="1.5"
      stroke-linecap="round"
      stroke-linejoin="round"
    />
  </svg>
{/snippet}

Anatomy

<script>
  import { Toolbar } from '@shardsui/svelte/toolbar'
</script>

<Toolbar.Root>
  <Toolbar.Button />
  <Toolbar.Link />
  <Toolbar.Separator />
  <Toolbar.Group>
    <Toolbar.Button />
    <Toolbar.Button />
  </Toolbar.Group>
  <Toolbar.Input />
</Toolbar.Root>

Usage guidelines

  • Name the toolbar and its groups: Toolbar.Root renders role="toolbar" and Toolbar.Group renders role="group", so give each an aria-label or aria-labelledby. Icon-only buttons need an aria-label of their own.
  • Use inputs sparingly: in a horizontal toolbar, left and right arrows both move the text cursor inside an input and step between the surrounding controls. To avoid that conflict, include at most one input and place it last.

Examples

Using with Select

Mount <Select.Root> inside the toolbar and let its own trigger be the control. <Select.Trigger> detects the surrounding toolbar and registers itself as a toolbar item, so it joins the arrow-key navigation and shares the toolbar's single tab stop. Toggle Group integrates the same way, and its toggles become the toolbar's items.

<Toolbar.Root>
  <Toolbar.Button>Bold</Toolbar.Button>
  <Toolbar.Separator />
  <Select.Root>
    <Select.Trigger>
      <Select.Value />
    </Select.Trigger>
    <!-- Compose the rest of the select -->
  </Select.Root>
</Toolbar.Root>

Using an input

Toolbar.Input joins the toolbar's single tab stop. Arrow-key focus selects the whole value, and an arrow key steps on to the next control only once the cursor sits collapsed at the matching edge of the text. Keep inputs to a minimum and place them last:

<Toolbar.Root>
  <Toolbar.Button>Bold</Toolbar.Button>
  <Toolbar.Input placeholder="Search" />
</Toolbar.Root>

Disabled items

Toolbar.Root and Toolbar.Group cascade disabled to the buttons and inputs inside them; links are never disabled. A disabled item keeps its place in the arrow-key sequence and carries aria-disabled instead of the native disabled attribute, so screen reader users still meet it:

<Toolbar.Root disabled>
  <Toolbar.Button>Bold</Toolbar.Button>
  <Toolbar.Button>Italic</Toolbar.Button>
</Toolbar.Root>

API reference

Root

A container for grouping a set of controls, such as buttons, toggle groups, or menus. Renders a <div> element with role="toolbar".

PropTypeDefault
AttributeDescription
data-disabledPresent when the toolbar is disabled.
data-orientationIndicates the orientation of the toolbar.

Keyboard:

The toolbar is one tab stop: Tab enters it at the item focused last — the first item to begin with — and leaves it altogether. Home and End are left to the focused control.

KeyAction
ArrowRight / ArrowLeftNext / previous item when horizontal. Swapped in RTL.
ArrowDown / ArrowUpNext / previous item when vertical.

Button

A button that can be used as-is or as a trigger for other components. Renders a <button> element.

PropTypeDefault
AttributeDescription
data-disabledPresent when the button is disabled.
data-orientationIndicates the orientation of the toolbar.

A link that joins the toolbar's arrow-key navigation. Renders an <a> element. Toolbar and group disabled never reach it.

PropTypeDefault
AttributeDescription
data-orientationIndicates the orientation of the toolbar.

Input

A native input that takes part in the toolbar's keyboard navigation. Renders an <input> element.

PropTypeDefault
AttributeDescription
data-disabledPresent when the input is disabled.
data-orientationIndicates the orientation of the toolbar.

Group

Groups several toolbar items or toggles. Renders a <div> element with role="group".

PropTypeDefault
AttributeDescription
data-disabledPresent when the group is disabled.
data-orientationIndicates the orientation of the toolbar.

Separator

A Separator oriented perpendicular to the toolbar. Renders a <div> element with role="separator".

PropTypeDefault
AttributeDescription
data-orientationIndicates the orientation of the separator, which is perpendicular to the toolbar.