# Utilities

> The shared helpers every component builds on, and how to use them in your own code.

URL: https://apogee.avaui.dev/docs/utilities
Last updated: 2026-10-15

The shared helpers every component builds on, and how to use them in your own code.

Last updated October 15, 2026

`npx @ava-ui/apogee add utils` copies `components/apogee/utils/`. Every component that needs one imports from there; so can you.

| Helper | Used by |
| --- | --- |
| `island` | Every component with enhancement JS |
| `uid` | Every component that renders an id (fields, menus, Tabs, Tooltip …) |
| `hotkey` | CommandPalette |
| `roving` | Tabs, ToggleButtonGroup |
| `fade` | ScrollArea, Table, Tabs |
| `scroll` | None yet; for your own overlays |
| `theme` | Theme, the docs toggle |
| `panel` / `menu` | Combobox, ContextMenu, Dropdown, Menubar, Popover, Select, Tooltip |
| `responsive` | Box, Carousel, Dialog, Grid, Stack, Steps, Text |
| `field` / `radius` | Text controls (Input, Select, Textarea …); Menubar, SegmentedControl, Tabs |
| `clipboard` | Code |
| `files` | FileUpload |
| `table-enhance` | DataGrid, Table |

## island

`island(selector, init)` runs `init` once per matching element, now and after every Astro View Transition. Document-level listeners stay outside `init`.

```ts
import { island } from '@/components/apogee/utils/island.ts'
island('.my-widget', (root) => { /* wire it */ })
```

## uid

`uid(Astro.locals, prefix?)` returns a deterministic id per request, so builds reproduce and two components on one page never collide.

## hotkey

`hotkey(combo, fn, { scope?, ignoreInputs? })` binds a shortcut and returns an unbind. `mod` is Meta on macOS and Control elsewhere; typing targets (inputs, textareas, contenteditable) are ignored unless `ignoreInputs: false`. CommandPalette uses it.

```ts
import { hotkey } from '@/components/apogee/utils/hotkey.ts'
const off = hotkey('mod+k', () => palette.showModal())
```

`parseHotkey('shift+/')` and `matchesHotkey(event, 'mod+k')` are exported for your own key handling. On macOS, Option changes the key value, so `alt+<letter>` combos do not match there; prefer `mod` and `shift`.

## roving

`stepIndex(key, current, last, rtl?)` returns the index an arrow, Home or End moves to (wrapping), or `null`. Tabs uses it. `rovingTabindex(root, { selector, loop? })` wires a whole row: one tab stop, arrows move it. ToggleButtonGroup uses it.

```ts
import { rovingTabindex } from '@/components/apogee/utils/roving.ts'
rovingTabindex(toolbar, { selector: 'button' })
```

## fade

`edgeFade(el, axis = 'x')` sets `data-fade="left right"` (or `top bottom`) on a scrolling box as it scrolls and resizes. Pair it with the mask utilities: `data-[fade~=left]:mask-l-from-85% data-[fade~=right]:mask-r-from-85%`. Tabs, Table and ScrollArea use it. `fadeState` / `fadeStateY` are the pure functions behind it.

## scroll

Modal dialogs lock the page through one rule in the tokens file (`body:has(dialog:modal)`). For a custom overlay call `lockScroll()` and `unlockScroll()`; locks are counted, so nested overlays release in order.

## theme

`getTheme()`, `setTheme(mode, { persist? })`, `toggleTheme()` and `resolveInverted(root)`. See [Theme](https://apogee.avaui.dev/docs/theme).

## panel and menu

`panelStyle({ anchor, placement, gap, width, maxHeight })` returns the inline anchor-positioning declarations every floating panel uses; `panelClass`, `panelSurface`, `menuClass` and the item tables style them. `menuKeydown(event, menu)` handles arrows, Home and End inside a `role="menu"`; `restoreFocus(panel, trigger)` returns focus when a nested popover closes. See [Popover](https://apogee.avaui.dev/docs/popover) and [Dropdown](https://apogee.avaui.dev/docs/dropdown).

## responsive

`responsive(value, table)` turns a `Responsive<T>` prop (`'row'` or `{ base: 'column', md: 'row' }`) into the class list from a generated table. Box, Carousel, Dialog, Grid, Stack, Steps and Text use it.

The tables live in `utils/responsive-tables.ts`, generated by `npm run build:responsive`; edit the script, not the file.

## field and radius

`fieldSizes`, `fieldVariants`, `fieldBase` are the shared text-control tables; `innerRadius['md-1']` gives a chip inside a rounded tray a concentric corner.

## clipboard

`copyText(text)` copies and resolves `true` on success. It uses the Clipboard API where the page is a secure context and falls back to a selected hidden textarea elsewhere (plain-HTTP pages, some in-app browsers). Code uses it.

## files

`acceptsFile(accept, file)` applies an `accept` list (`.pdf`, `image/*`, `image/png`) to a `{ name, type }`, the rule the native picker uses, so dropped files get the same check. `readableSize(bytes)` gives `1.5 KB`. FileUpload uses both.
