Island

A floating pill that changes with what the viewer is doing. Each state is a view, and switching views morphs the pill to its new size while the contents crossfade in place.

Koala journal

Notes from building a design system

Installation#

# One-time setup (tokens + lib helpers)npx koalaui-cli@latest init# Add this component (its dependencies come along)npx koalaui-cli@latest add island

Usage#

usage.tsxTSX
import {  Island,  IslandView,  IslandSwap,  IslandButton,  IslandIcon,  IslandSeparator,  IslandTitle,  IslandProgress,} from "@/components/ui/island"<Island  aria-label="Page"  view={reading ? "reading" : "browse"}  className="fixed bottom-6 left-1/2 z-50 -translate-x-1/2">  <IslandView value="browse">    <IslandButton tooltip="Dark theme" shortcut="L">      <Moon weight="bold" />    </IslandButton>  </IslandView>  <IslandView value="reading">    <IslandButton tooltip="Back">      <ArrowLeft weight="bold" />    </IslandButton>    <IslandSeparator />    <IslandTitle>{post.title}</IslandTitle>  </IslandView></Island>

Mount it once, in the layout, and drive view from the route. An island that each page renders for itself is a new island on every navigation, so it can only appear, never morph. Pin it yourself, the way you would any floating surface: fixed and centred, so it grows out from its middle, above env(safe-area-inset-bottom) on a phone.

Variants#

Floating is the default: the elevated popover surface, with the controls resting muted until one of them holds a state. Solid is the inverted slab, the closest thing to the hardware island and the one to reach for over a photograph, a video or a canvas. It flips foreground and background instead of hardcoding black, so it stays legible in all four themes.

floating
solid

Sizes#

size is the island's own density axis, not the page's comfortable/compact knob: a floating control bar that shrinks to form-compact becomes hard to hit. md is the original's 44px pill and lg the touch step. At every size, padding plus half a control is half the pill, so the round controls sit concentric in its ends.

sm
md
lg

Views#

Declare every state as an IslandView and name the one on screen with view. Only that view is mounted, plus, for one beat, the one leaving. The pill eases to the new width and height while the old contents blur out and the new ones blur in. A view is a row; give it flex-col and the pill grows into a card, its corner easing from half the height to the card's radius on the same clock. IslandIcon holds a status glyph at a control's size, and IslandProgress is the readout and bar for a task that is running.

Swap#

IslandSwap is a region of a view that changes on its own, with the same crossfade and a small morph of its own: the title that gives way to the progress readout once you scroll. Name a view no child answers to and the region empties and collapses to nothing, which is how a “back to top” grows into the pill past the fold and out of it again at the top.

Buttons and menus#

One glyph makes an IslandButton a circle, a word makes it a pill. pressed lights the chip for a toggle that is on, shortcut puts a keycap in the tooltip, and caret turns it into a menu trigger whose caret points at the menu while it is open. Open menus upward, away from the edge the island sits on, and keep the Tooltip outside the trigger so the menu keeps its own hold on the button.

API reference#

Island

The morphing pill and the roving-focus root (Radix Toolbar.Root, so it forwards dir, loop and the rest). Takes view (the value of the view on screen), variant (floating · solid), size (sm · md · lg) and tooltipPlacement (default "top"). Without views, its children are simply its contents, and it still eases when they change size.

IslandView

One state, named by value. Mounted while it is on screen; while it leaves it is out of flow, inert and hidden from assistive tech. A row by default; className can make it a card.

IslandSwap

A region inside a view with its own view and its own IslandView children. A value no child answers to collapses it to nothing.

IslandButton

A control (Radix Toolbar.Button). Takes tooltip (a string also becomes its aria-label), shortcut, pressed, caret, disabled and asChild.

IslandIcon · IslandTitle · IslandProgress · IslandSeparator

IslandIcon is a status glyph at a control's size. IslandTitle is one line that truncates before it stretches the pill. IslandProgress takes value (0 to 100, or null for an unknown length), transition and an aria-label; its bar is our Progress. IslandSeparator is the hairline between clusters.

FAQ#

Toolbar is a band of controls that keeps one shape. Dock is navigation that changes shape on demand: a rail you expand into a labelled menu. Island changes with context rather than on demand: its views follow what the viewer is doing (browsing, reading, waiting on a task), and it swaps them in place. All three sit on Radix Toolbar, so they share the roving focus and the arrow keys.