Detent web components
By Shane Logsdon

Feedback & status

Status Island

Implemented

Continuous physical morphing island with spring layout physics

detent-status-island

Port status
Catalog status
implemented
Origin
upstream
Phase
3
Wave
phase-3-overlays
Group
Feedback & status
Open to work

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

Variants

The pill morphs between collapsed, expanded, profile and share. Tab walks the controls, Escape collapses from any open state, and an outside press collapses it too.

No state change yet.

Recorded states

  • closed recorded state; no setter
  • expanded recorded state; no setter
  • collapsed recorded state; no setter
  • dark theme recorded state; no setter
  • reduced motion recorded state; no setter
  • profile recorded state; no setter
  • share recorded state; no setter

Usage

Import

import '/components/status-island/define.js';

// or register it yourself
import { DetentStatusIsland } from '/components/status-island/status-island.js';

Attributes

  • avatar
  • avatar-alt
  • default-state
  • description
  • greeting
  • id-prefix
  • name
  • job-title
  • state
  • status-text

Properties

  • metadata
  • onStateChange
  • socials

Slots

  • default
  • profile-content
  • share-content

Events

  • detent-state-change — {}

Example

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

<detent-status-island
  avatar
  avatar-alt
  default-state="collapsed"
  description="Building thoughtful interfaces..."
  greeting="Hello, I am [name]"
  id-prefix
  name="Suraj Maurya"
  job-title="Frontend Developer"
  state
  status-text="Available for work"
></detent-status-island>

Property-only values

const element = document.querySelector('detent-status-island');
if (element !== null) {
  // metadata: DynamicIslandMetadataItem[]
  element.metadata = [];
  // socials: DynamicIslandSocials
  element.socials = undefined;
  // onStateChange: (state: DynamicIslandState) => void
  element.onStateChange = () => {};
}

Source

The pinned upstream source for this row:

  • src/components/ui/DynamicIsland.tsx sha256 87cc23102b95…

Native files

The native module and its artifacts:

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

API

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

Attributes

Attributes
AttributePropertyTypeDefaultNotes
avataravatarstringundefinedKebab-case attribute; camelCase property.
avatar-altavatarAltstringundefinedKebab-case attribute; camelCase property.
default-statedefaultState'collapsed' | 'expanded' | 'profile' | 'share''collapsed'Kebab-case attribute; camelCase property.
descriptiondescriptionstring'Building thoughtful interfaces...'Kebab-case attribute; camelCase property.
greetinggreetingstring'Hello, I am [name]'Kebab-case attribute; camelCase property.
id-prefix———Recorded in the catalog as an attribute.
namenamestring'Suraj Maurya'Kebab-case attribute; camelCase property.
job-titlejobTitlestring'Frontend Developer'Kebab-case attribute; camelCase property.
statestate'collapsed' | 'expanded' | 'profile' | 'share'undefinedKebab-case attribute; camelCase property.
status-textstatusTextstring'Available for work'Kebab-case attribute; camelCase property.

Properties

Properties
PropertyTypeDefaultNotes
metadataDynamicIslandMetadataItem[]undefinedProperty-only: assign from script; not accepted as an attribute.
onStateChange(state: DynamicIslandState) => voidundefinedProperty-only: assign from script; not accepted as an attribute.
socialsDynamicIslandSocials5 default linksProperty-only: assign from script; not accepted as an attribute.

Events

Events
EventUpstream propPayload typeNotes
detent-state-changeonStateChange(state: DynamicIslandState) => voidComposed, bubbles; fires only for a user action.

Slots

Slots
SlotNotes
defaultConsumer content; rendered through a slot, never re-created.
profile-contentConsumer content; rendered through a slot, never re-created.
share-contentConsumer content; rendered through a slot, never re-created.

CSS customisation

  • React `children`/`ReactNode` props become DOM slots; caller content stays light DOM.
  • `className` prop is replaced by documented CSS parts/custom properties; no Tailwind in consumers.
  • 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.
  • state is the observable value. Upstream's controlled/uncontrolled split (state vs defaultState) becomes "state reflects the current state, default-state seeds it", so all four catalogued states stay inspectable and styleable without a second attribute (PC-3.3).
  • A polite live region announces the state change. Upstream had none; the port announces each user transition once and never for programmatic writes (PC-12.5).
  • Focus is moved deliberately after a transition. Upstream replaced the view without moving focus, leaving focus on a removed node; the port focuses the new view's first control (PC-5.1, PC-12.6).
  • The social dock validates URLs. Upstream assigned href unvalidated; a refused URL now becomes an inert element instead of a link (R-3.2).
  • No layoutId avatar projection. The avatar is rendered per view; the surface morph is a spring on width/height/radius, which keeps the pill geometry continuous without shared-layout projection (PC-10.2).
  • className is replaced by documented parts and custom properties; icon (a React node per social item) is replaced by type-driven local SVGs (PC-11.5).
  • The status pulse is disabled under reduced motion, and the dot uses the text token so it is perceivable in both schemes (PC-10.5, WCAG 1.4.11).
  • Slots replace React nodes: children → the default slot, profileContent → profile-content, shareContent → share-content (PC-11.2).
  • The upstream `role` prop is ported as `job-title`/`jobTitle`: `role` is a reserved global ARIA attribute, so using it for the person's job title put an invalid ARIA role on the host element (axe reports it as critical).

What it does

id-prefix seeds the shadow ids (<prefix>-island, <prefix>-surface, <prefix>-live).

Upstream behaviour
  • Continuous physical morphing between collapsed, expanded, profile, and share states
  • Spring-based layout-aware geometry and radius interpolation with no abrupt snapping
  • Persistent spatial continuity for the avatar across collapsed and profile states
  • Fully accessible keyboard navigation with Escape key dismissal and click-outside collapse
  • Configurable social dock supporting predefined platform detection and custom links
  • Light and dark mode compatibility adhering strictly to the EasyUI design token system
  • Respects prefers-reduced-motion media query with instant fallback states
Differences from upstream
  • React `children`/`ReactNode` props become DOM slots; caller content stays light DOM.
  • `className` prop is replaced by documented CSS parts/custom properties; no Tailwind in consumers.
  • 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.
  • state is the observable value. Upstream's controlled/uncontrolled split (state vs defaultState) becomes "state reflects the current state, default-state seeds it", so all four catalogued states stay inspectable and styleable without a second attribute (PC-3.3).
  • A polite live region announces the state change. Upstream had none; the port announces each user transition once and never for programmatic writes (PC-12.5).
  • Focus is moved deliberately after a transition. Upstream replaced the view without moving focus, leaving focus on a removed node; the port focuses the new view's first control (PC-5.1, PC-12.6).
  • The social dock validates URLs. Upstream assigned href unvalidated; a refused URL now becomes an inert element instead of a link (R-3.2).
  • No layoutId avatar projection. The avatar is rendered per view; the surface morph is a spring on width/height/radius, which keeps the pill geometry continuous without shared-layout projection (PC-10.2).
  • className is replaced by documented parts and custom properties; icon (a React node per social item) is replaced by type-driven local SVGs (PC-11.5).
  • The status pulse is disabled under reduced motion, and the dot uses the text token so it is perceivable in both schemes (PC-10.5, WCAG 1.4.11).
  • Slots replace React nodes: children → the default slot, profileContent → profile-content, shareContent → share-content (PC-11.2).
  • The upstream `role` prop is ported as `job-title`/`jobTitle`: `role` is a reserved global ARIA attribute, so using it for the person's job title put an invalid ARIA role on the host element (axe reports it as critical).
Previous: Checkbox Next: Hanging Amulet

Docs

Components

Project