Patternmode

Verge

A reserved slot whose controls rest hidden and reveal on hover, on focus, and always on touch — holding their space throughout, so nothing moves.

Install

npm install @patternmode/verge
import { Verge } from "@patternmode/verge";
import "@patternmode/verge/styles.css";

export function FinishRow({ finish }) {
  return (
    <Verge.Root as="li">
      <span>{finish.name}</span>
      <Verge.Slot slots={2}>
        <IconButton label="Rename" />
        <OverflowMenu />
      </Verge.Slot>
    </Verge.Root>
  );
}

What it already handles

The reveal contract

Registries ship the button, the dropdown and the tooltip — the things being revealed. None ships the contract governing when they appear, which is where hand-rolled versions drift apart.

Keyboard
:focus-withinTabbing into the row reveals the slot, via :focus-within on the root. Children stay in the tab order at rest, which is what lets focus arrive at all. Spelling this focus-visible is the defect that recurs: it reveals the overflow menu two elements away but not the checkbox beside it.
Touch
@media (hover: none)No hover exists on a touch surface, so nothing hides. Handled with a media query, not a tap handler. Controls also take a wider gap on coarse pointers, because permanently-visible controls 2px apart are not separable by a thumb.
Open menus
[data-popup-open] | [data-state="open"] | [aria-expanded="true"]A slot holding an open menu, popover or disclosure stays revealed, so the overflow control does not vanish when the pointer travels to the menu it just opened.
Layout stability
opacity + pointer-eventsOnly opacity and pointer-events change. The slot occupies its space at rest and revealed alike, so the row's geometry never shifts under the pointer.
Nesting
inherited custom propertiesRoots may nest. A nested root shadows its ancestor's state for its own subtree, so pointing at an outer row does not reveal an inner row's controls.
Reduced motion
@media (prefers-reduced-motion: reduce)The transition is dropped under prefers-reduced-motion: reduce.

Core API

Verge.Root

The container whose hover and focus state the slots read. Wrap the whole row, not just the controls — the reveal responds to the user addressing the row. Contributes no appearance of its own.

as
ElementType"div"Element or component to render as, so a row inside a list can be an li and a cell can be a td.

Verge.Slot

The reserved area holding the controls.

slots
numberHow many controls' worth of width to hold open. Only needed when controls must align into a column across rows carrying different numbers of them. Width per control comes from --patternmode-verge-slot-size.
visible
booleanfalseKeeps the slot revealed regardless of pointer or focus, for state the CSS cannot see such as an action mid-flight. Not an escape hatch for one row mattering more — a control drawn on every row at rest is furniture.
as
ElementType"div"Element or component to render as.

What you must handle: touch hit areas

/* Verge reserves the slot; it does not own your button. On touch the
   controls are permanently visible, so each needs a 48x48px hit area. */
@custom-variant pointer-fine (@media (pointer: fine));

Theming

Custom properties

Every knob is a custom property, so retune without forking.

--patternmode-verge-slot-size
length1.75remWidth one control reserves, used by slots.
--patternmode-verge-duration
time120msReveal transition duration.
--patternmode-verge-easing
easing-functioncubic-bezier(0.2, 0, 0, 1)Reveal transition easing.