Orbex Sonar-DB Design system , home

Examples

Actions on an order
Code
tsx
import { Button } from '@orbex/sonar-components/button'
import { Menu } from '@orbex/sonar-components/menu'
import type { ComponentProps } from 'react'
import copySymbol from '@material-symbols/svg-400/rounded/content_copy.svg?raw'
import deleteSymbol from '@material-symbols/svg-400/rounded/delete.svg?raw'
import downloadSymbol from '@material-symbols/svg-400/rounded/download.svg?raw'
import expandSymbol from '@material-symbols/svg-400/rounded/keyboard_arrow_down.svg?raw'
import printSymbol from '@material-symbols/svg-400/rounded/print.svg?raw'
import shareSymbol from '@material-symbols/svg-400/rounded/share.svg?raw'

/* Material Symbols Rounded, weight 400, as icon components (Foundations › Icons) */
const symbol = (svg: string) => {
  const path = svg.match(/ d="([^"]+)"/)?.[1]
  return (props: ComponentProps<'svg'>) => (
    <svg viewBox="0 -960 960 960" fill="currentColor" aria-hidden="true" focusable="false" {...props}>
      <path d={path} />
    </svg>
  )
}

const CopyIcon = symbol(copySymbol)
const DeleteIcon = symbol(deleteSymbol)
const DownloadIcon = symbol(downloadSymbol)
const ExpandIcon = symbol(expandSymbol)
const PrintIcon = symbol(printSymbol)
const ShareIcon = symbol(shareSymbol)

export default function Actions() {
  return (
    <Menu.Root>
      <Menu.Trigger render={<Button variant="outlined" iconEnd={<ExpandIcon />} />}>Actions</Menu.Trigger>
      <Menu.Content>
        <Menu.Item icon={<DownloadIcon />} trailing="CSV">
          Export
        </Menu.Item>
        <Menu.Item icon={<PrintIcon />}>Print</Menu.Item>
        <Menu.Item icon={<ShareIcon />}>Share link</Menu.Item>
        <Menu.Item icon={<CopyIcon />}>Duplicate order</Menu.Item>
        <Menu.Separator />
        <Menu.Item icon={<DeleteIcon />} tone="danger">
          Cancel order
        </Menu.Item>
      </Menu.Content>
    </Menu.Root>
  )
}
A choice inside a menu
Code
tsx
import { IconButton } from '@orbex/sonar-components/icon-button'
import { Menu } from '@orbex/sonar-components/menu'
import { useState } from 'react'
import type { ComponentProps } from 'react'
import moreSymbol from '@material-symbols/svg-400/rounded/more_vert.svg?raw'
import refreshSymbol from '@material-symbols/svg-400/rounded/refresh.svg?raw'

/* Material Symbols Rounded, weight 400, as icon components (Foundations › Icons) */
const symbol = (svg: string) => {
  const path = svg.match(/ d="([^"]+)"/)?.[1]
  return (props: ComponentProps<'svg'>) => (
    <svg viewBox="0 -960 960 960" fill="currentColor" aria-hidden="true" focusable="false" {...props}>
      <path d={path} />
    </svg>
  )
}

const MoreIcon = symbol(moreSymbol)
const RefreshIcon = symbol(refreshSymbol)

export default function Units() {
  const [unit, setUnit] = useState('plt')
  return (
    <Menu.Root>
      <Menu.Trigger render={<IconButton label="Inventory options" icon={<MoreIcon />} />} />
      <Menu.Content align="end">
        <Menu.Group>
          <Menu.GroupLabel>Show figures in</Menu.GroupLabel>
          <Menu.RadioGroup value={unit} onValueChange={(value) => setUnit(String(value))}>
            <Menu.RadioItem value="plt">Pallets</Menu.RadioItem>
            <Menu.RadioItem value="kg">Kilograms</Menu.RadioItem>
            <Menu.RadioItem value="items">Items</Menu.RadioItem>
          </Menu.RadioGroup>
        </Menu.Group>
        <Menu.Separator />
        <Menu.Item icon={<RefreshIcon />}>Refresh stock</Menu.Item>
      </Menu.Content>
    </Menu.Root>
  )
}

Sizes, variants and states

Kinds of item and their states

Usage

  • For actions on the thing in view. Choices that change what is shown can live in a menu as a radio group.
  • Destructive actions go last, after a separator, in the danger tone. They still need a confirmation or an undo.
  • The trigger keeps its look: pass a Button or an IconButton with render.

Do and don’t

DoVerbs, grouped; the destructive item last, after a separator, in danger.
Don’tA destructive item among the others, one slip away from the common ones.
DoA short menu of actions on one thing.
Don’tA long list of places or records. That is navigation, a select or search.

Accessibility

Keyboard

Key What it does
Enter, Space or Down arrow On the trigger: opens the menu and focuses the first item.
Up and Down arrows Move between items; disabled items are skipped.
Home and End Go to the first or the last item.
A letter Moves to the next item that starts with it.
Enter or Space Activates the item, or chooses it in a group of choices.
Escape Closes the menu and returns focus to the trigger.
Tab Closes the menu and moves on.

Roles and attributes

Element or attribute What it gives
aria-haspopup, aria-expanded On the trigger: it opens a menu, and whether it is open.
role="menu" The popup; items are menuitem, or menuitemradio with aria-checked in a group of choices.
Group labels Name their group, read when focus enters it.

Notes

  • The trigger announces that it opens a menu, and whether it is open.
  • Arrow keys move between items, typing jumps to an item, Escape closes and returns focus to the trigger.
  • The highlight stays neutral behind danger items, so their red text keeps 4.5:1.

Use it

Each component is its own entry point with its own stylesheet, so an app loads only what it uses. The tokens and the cascade layers are set once for the app: see Installation.

tsx
import { Menu } from '@orbex/sonar-components/menu'
import '@orbex/sonar-components/menu.css'

API

Menu.Root

Holds the menu’s open state.

Passes every other prop to Base UI: see the Base UI reference.

Menu.Trigger

The element that opens the menu. Pass a Button or an IconButton with render, so the trigger keeps its look.

Passes every other prop to Base UI: see the Base UI reference.

Menu.Content

The popup, positioned against the trigger and rendered at the end of the document.

Props of Menu.Content
Prop Type Default
className

Classes added to the menu’s popup, after sonar-menu: a string, or a function of the menu’s state.

ClassName —
side

Side of the trigger the menu opens on. It flips when there is no room.

BaseMenu.Positioner.Props['side'] 'bottom'
align

How the menu lines up with its trigger along that side: start, center or end. A menu in a card header aligns to the end, so it opens inward.

BaseMenu.Positioner.Props['align'] 'start'
sideOffset

Distance from the trigger, in pixels.

number 6

Passes every other prop to Base UI: see the Base UI reference.

Menu.Item

An action.

Props of Menu.Item
Prop Type Default
className

Classes added to the item, after its own: a string, or a function of its state.

ClassName —
icon

Icon before the label.

ReactNode —
trailing

Text at the end of the row, such as a shortcut or a count.

ReactNode —
tone

Danger for destructive actions: the label turns red, and the action should still be confirmed or undoable.

'default' | 'danger' 'default'

Passes every other prop to Base UI: see the Base UI reference.

Menu.Separator

A rule between groups of items.

Props of Menu.Separator
Prop Type Default
className

Classes added to the separator, after its own: a string, or a function of its state.

ClassName —

Passes every other prop to Base UI: see the Base UI reference.

Menu.Group

Items that belong together, named by a GroupLabel.

Passes every other prop to Base UI: see the Base UI reference.

Menu.GroupLabel

The name of a group, read when focus enters it.

Props of Menu.GroupLabel
Prop Type Default
className

Classes added to the group label, after its own: a string, or a function of its state.

ClassName —

Passes every other prop to Base UI: see the Base UI reference.

Menu.RadioGroup

A set of items where exactly one is chosen, such as the unit figures are shown in.

Passes every other prop to Base UI: see the Base UI reference.

Menu.RadioItem

One choice in a RadioGroup, with a check when chosen.

Props of Menu.RadioItem
Prop Type Default
className

Classes added to the radio item, after its own: a string, or a function of its state.

ClassName —

Passes every other prop to Base UI: see the Base UI reference.