Detent web components
By Shane Logsdon

Buttons & actions

Split Button

Implemented

A primary action with a chevron trigger for related actions.

detent-split-button

Port status
Catalog status
implemented
Origin
upstream
Phase
2
Wave
phase-2-controls
Group
Buttons & actions

6.8 KB gzip for the class module · inlines the shared spring

Variants

primary
secondary
danger
info
disabled

Recorded states

  • disabled recorded state; no setter
  • open recorded state; no setter
  • expanded recorded state; no setter
  • collapsed recorded state; no setter
  • reduced motion recorded state; no setter
  • active recorded state; no setter

Usage

Import

import '/components/split-button/define.js';

// or register it yourself
import { DetentSplitButton } from '/components/split-button/split-button.js';

Attributes

  • disabled
  • label
  • size
  • variant

Properties

  • label
  • options
  • size
  • variant
  • disabled
  • open
  • onClick

Slots

No slots are recorded for this component.

Events

  • detent-select — { value: string; label: string }
  • detent-click — {}

Example

Runnable example: site/examples/split-button.ts. The snippet below is generated from the catalogued attributes.

<detent-split-button
  disabled
  label
  size="md"
  variant="primary"
></detent-split-button>

Property-only values

const element = document.querySelector('detent-split-button');
if (element !== null) {
  // onClick: () => void
  element.onClick = () => {};
  // options: SplitButtonOption[]
  element.options = [];
}

Source

The pinned upstream source for this row:

  • src/components/ui/SplitButton.tsx sha256 0747f656021b…

Native files

The native module and its artifacts:

  • src/components/split-button/split-button.ts
  • src/components/split-button/styles.css
  • src/components/split-button/define.ts
  • docs/components/split-button.md
  • site/examples/split-button.ts
  • tests/browser/split-button.spec.ts
  • evidence/components/split-button.md

API

Boolean attributes follow HTML presence semantics: present means true, absent means false, and disabled="false" still means disabled.

Attributes

Attributes
AttributePropertyTypeDefaultNotes
disableddisabledbooleanfalsePresence semantics: present means true, absent means false.
labellabelstring—Kebab-case attribute; camelCase property.
sizesize'sm' | 'md' | 'lg''md'Kebab-case attribute; camelCase property.
variantvariant'primary' | 'secondary''primary'Kebab-case attribute; camelCase property.

Properties

Properties
PropertyTypeDefaultNotes
labelstring—Property-only: assign from script; not accepted as an attribute.
optionsSplitButtonOption[]—Property-only: assign from script; not accepted as an attribute.
size'sm' | 'md' | 'lg''md'Property-only: assign from script; not accepted as an attribute.
variant'primary' | 'secondary''primary'Property-only: assign from script; not accepted as an attribute.
disabledbooleanfalseProperty-only: assign from script; not accepted as an attribute.
open——Recorded in the catalog without a top-level type; pinned from source when the row is ported.
onClick() => void—Property-only: assign from script; not accepted as an attribute.

Events

Events
EventUpstream propPayload typeNotes
detent-selectoptions[].onSelect() => voidComposed, bubbles; fires only for a user action.
detent-clickonClick() => voidComposed, bubbles; fires only for a user action.

Slots

No slots are recorded for this component.

CSS customisation

  • React callback props become typed composed CustomEvents; programmatic state assignment emits none.
  • Framer Motion springs/AnimatePresence ported to CSS transitions, WAAPI or the shared rAF spring helper.
  • `lucide-react` icon components replaced with local SVG assets (ISC notice retained).
  • `onSelect` becomes the composed `detent-select` event with a plain-data detail; the React `icon` node and the per-option callback are dropped from `options`.
  • Real roving DOM focus over enabled menuitem buttons replaces upstream's activeIndex highlight on the menu container; Tab closes the menu and hands focus to the next tabbable element outside the control (PC-12.6).
  • Framer Motion springs and AnimatePresence become the shared spring helper with the pinned springSnappy values (400/25/0.5); the menu closes without an exit animation.
  • The upstream hard-coded light/dark hex pairs and opacity-30 disabled treatment become theme tokens and muted tokens at full opacity (PC-9.5).
  • `open` is a reflected state attribute, not a controlled prop.
  • The pinned source also styles danger and info variants; both are ported (danger #b91c1c fill, info #1d4ed8 fill, hover one step lighter) so the variant list matches the source rather than the previously recorded two.

What it does

A primary action fused with a chevron trigger that reveals related actions in a menu. Two real buttons, a real role="menu" with roving focus, one shared pill silhouette.

Upstream behaviour
  • Primary action and chevron trigger share a single rounded pill silhouette
  • Chevron rotates with a snappy spring on open/close
  • Menu flips above the trigger when there is not enough room below
  • Supports primary and secondary visual variants
  • Closes on outside click and on Escape
Differences from upstream
  • React callback props become typed composed CustomEvents; programmatic state assignment emits none.
  • Framer Motion springs/AnimatePresence ported to CSS transitions, WAAPI or the shared rAF spring helper.
  • `lucide-react` icon components replaced with local SVG assets (ISC notice retained).
  • `onSelect` becomes the composed `detent-select` event with a plain-data detail; the React `icon` node and the per-option callback are dropped from `options`.
  • Real roving DOM focus over enabled menuitem buttons replaces upstream's activeIndex highlight on the menu container; Tab closes the menu and hands focus to the next tabbable element outside the control (PC-12.6).
  • Framer Motion springs and AnimatePresence become the shared spring helper with the pinned springSnappy values (400/25/0.5); the menu closes without an exit animation.
  • The upstream hard-coded light/dark hex pairs and opacity-30 disabled treatment become theme tokens and muted tokens at full opacity (PC-9.5).
  • `open` is a reflected state attribute, not a controlled prop.
  • The pinned source also styles danger and info variants; both are ported (danger #b91c1c fill, info #1d4ed8 fill, hover one step lighter) so the variant list matches the source rather than the previously recorded two.
Previous: Speed Warp Next: Spotlight Card

Docs

Components

Project