Detent web components
By Shane Logsdon

Navigation

Hamburger Menu

Implemented

Architectural line morphing & symmetrical spring motion

detent-hamburger-menu

Port status
Catalog status
implemented
Origin
upstream
Phase
2
Wave
phase-2-navigation
Group
Navigation

2.5 KB gzip for the class module

Variants

Closed (the preview above), customised (size="40", color="#8fb0ff"), already open (is-open) and disabled. Activation by click, Space or Enter emits one detent-change with { isOpen }; Escape closes an open menu. The lines are aria-hidden decoration and the button's label switches between Open <label> and Close <label>.

Recorded states

  • focus-visible recorded state; no setter
  • disabled recorded state; no setter
  • open recorded state; no setter
  • expanded recorded state; no setter

Usage

Import

import '/components/hamburger-menu/define.js';

// or register it yourself
import { DetentHamburgerMenu } from '/components/hamburger-menu/hamburger-menu.js';

Attributes

  • color
  • disabled
  • is-open
  • label
  • size

Properties

  • isOpen
  • size
  • color
  • label
  • disabled
  • onChange

Slots

No slots are recorded for this component.

Events

  • detent-change — { isOpen: boolean }

Example

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

<detent-hamburger-menu
  color="currentColor"
  disabled
  is-open
  label="Menu"
  size="24"
></detent-hamburger-menu>

Property-only values

const element = document.querySelector('detent-hamburger-menu');
if (element !== null) {
  // onChange: (isOpen: boolean) => void
  element.onChange = () => {};
}

Source

The pinned upstream source for this row:

  • src/components/ui/HamburgerMenu.tsx sha256 cf8c257f7551…

Native files

The native module and its artifacts:

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

API

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

Attributes

Attributes
AttributePropertyTypeDefaultNotes
colorcolorstring'currentColor'Kebab-case attribute; camelCase property.
disableddisabledbooleanfalsePresence semantics: present means true, absent means false.
is-openisOpenboolean—Presence semantics: present means true, absent means false.
labellabelstring'Menu'Kebab-case attribute; camelCase property.
sizesizenumber24Kebab-case attribute; camelCase property.

Properties

Properties
PropertyTypeDefaultNotes
isOpenboolean—Property-only: assign from script; not accepted as an attribute.
sizenumber24Property-only: assign from script; not accepted as an attribute.
colorstring'currentColor'Property-only: assign from script; not accepted as an attribute.
labelstring'Menu'Property-only: assign from script; not accepted as an attribute.
disabledbooleanfalseProperty-only: assign from script; not accepted as an attribute.
onChange(isOpen: boolean) => void—Property-only: assign from script; not accepted as an attribute.

Events

Events
EventUpstream propPayload typeNotes
detent-changeonChange(isOpen: boolean) => 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.
  • React state/hooks replaced by element-owned state with pre-upgrade property preservation.
  • Upstream is controlled (`isOpen` + `onChange` required, no default); the element owns the state, defaults `isOpen` to false and reflects `is-open` (the catalog `null` means required upstream, with no default).
  • `onChange` becomes the `detent-change` event; Escape-to-close implements the recorded catalog feature the pinned source lacks (only while focused and open).
  • `disabled:opacity-40` becomes a full-opacity icon with `cursor: not-allowed` (PC-12.6); the springSnappy morph becomes a CSS transform/opacity transition; `className` maps to parts and properties.

What it does

A three-line menu toggle that morphs into a symmetric close mark with zero layout shift.

Upstream behaviour
  • Symmetric line rotation and middle line dissolution with zero layout shift
  • Customizable size, stroke thickness, and color tokens
  • Keyboard accessible with Space and Enter triggers and Escape close support
  • Integrated aria-expanded and dynamic aria-label status updates
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.
  • React state/hooks replaced by element-owned state with pre-upgrade property preservation.
  • Upstream is controlled (`isOpen` + `onChange` required, no default); the element owns the state, defaults `isOpen` to false and reflects `is-open` (the catalog `null` means required upstream, with no default).
  • `onChange` becomes the `detent-change` event; Escape-to-close implements the recorded catalog feature the pinned source lacks (only while focused and open).
  • `disabled:opacity-40` becomes a full-opacity icon with `cursor: not-allowed` (PC-12.6); the springSnappy morph becomes a CSS transform/opacity transition; `className` maps to parts and properties.
Previous: Gravity Particle Burst Next: Area Chart

Docs

Components

Project