Detent web components
By Shane Logsdon

Buttons & actions

Typewriter Button

Implemented

Character-by-character reveal with mechanical sound feedback

detent-typewriter-button

Port status
Catalog status
implemented
Origin
upstream
Phase
4
Wave
phase-4-motion-controls
Group
Buttons & actions
npm install detent-ui

4.3 KB gzip for the class module

Variants

Deploying to production Connecting Auto-start reveal Clack

No button has been activated yet.

Recorded states

  • reduced motion recorded state; no setter
  • completed recorded state; no setter

Usage

Import

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

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

Attributes

  • auto-start
  • char-duration
  • sound-enabled
  • sound-volume
  • variant

Properties

  • onClick
  • onComplete

Slots

  • default

Events

  • detent-complete — {}

Example

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

<detent-typewriter-button
  auto-start
  char-duration="75"
  sound-enabled
  sound-volume="0.25"
  variant="primary"
></detent-typewriter-button>

Property-only values

const element = document.querySelector('detent-typewriter-button');
if (element !== null) {
  // onComplete: () => void
  element.onComplete = () => {};
}

Source

The pinned upstream source for this row:

  • src/components/ui/TypewriterButton.tsx sha256 5b9acdba2332…

Native files

The native module and its artifacts:

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

API

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

Attributes

Attributes
AttributePropertyTypeDefaultNotes
auto-startautoStartbooleanfalsePresence semantics: present means true, absent means false.
char-durationcharDurationnumber75Kebab-case attribute; camelCase property.
sound-enabledsoundEnabledbooleanfalsePresence semantics: present means true, absent means false.
sound-volumesoundVolumenumber0.25Kebab-case attribute; camelCase property.
variantvariant'primary' | 'secondary' | 'outline''primary'Kebab-case attribute; camelCase property.

Properties

Properties
PropertyTypeDefaultNotes
onClick——Recorded in the catalog without a top-level type; pinned from source when the row is ported.
onComplete() => voidundefinedProperty-only: assign from script; not accepted as an attribute.

Events

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

Slots

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

CSS customisation

  • React `children`/`ReactNode` props become DOM slots; caller content stays light DOM.
  • 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.
  • Reduced motion renders the full label in the activation task.
  • One lazily created AudioContext is closed on disconnect.
  • auto-start runs once per connection and never restarts a completed element.
  • The outline variant ships #090909 as its fill instead of transparent, which measured 1.05:1.
  • char-duration and sound-volume are validated and clamped with one diagnostic each.
  • There is no disabled surface because the row records none.
  • Activation is the native composed click plus the onClick hook and detent-complete; the detent-click companion the row originally recorded was dropped on 2026-10-26 after the PC-6.1 review, and the row is corrected here.

What it does

A button whose label types itself out character by character, with an optional synthesized mechanical keystroke sound (Web Audio, no audio asset) and a blinking cursor. The accessible name is always the complete label, never the revealed prefix.

Upstream behaviour
  • Progressive character-by-character typewriter reveal with blinking cursor
  • Synthesized mechanical click sound generated dynamically via Web Audio API (zero audio file assets)
  • Configurable typing speed, sound volume, variants, and auto-start mode
  • Full accessibility with complete aria-label announcement and keyboard triggers
Differences from upstream
  • React `children`/`ReactNode` props become DOM slots; caller content stays light DOM.
  • 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.
  • Reduced motion renders the full label in the activation task.
  • One lazily created AudioContext is closed on disconnect.
  • auto-start runs once per connection and never restarts a completed element.
  • The outline variant ships #090909 as its fill instead of transparent, which measured 1.05:1.
  • char-duration and sound-volume are validated and clamped with one diagnostic each.
  • There is no disabled surface because the row records none.
  • Activation is the native composed click plus the onClick hook and detent-complete; the detent-click companion the row originally recorded was dropped on 2026-10-26 after the PC-6.1 review, and the row is corrected here.
Previous: Torque Dial Next: Undo Toast

Docs

Components

Project