Buttons & actions
Typewriter Button
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
4.3 KB gzip for the class module
Variants
No button has been activated yet.
Recorded states
reduced motionrecorded state; no settercompletedrecorded 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-startchar-durationsound-enabledsound-volumevariant
Properties
onClickonComplete
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.tsxsha256 5b9acdba2332…
Native files
The native module and its artifacts:
src/components/typewriter-button/typewriter-button.tssrc/components/typewriter-button/styles.csssrc/components/typewriter-button/define.tsdocs/components/typewriter-button.mdsite/examples/typewriter-button.tstests/browser/typewriter-button.spec.tsevidence/components/typewriter-button.md
API
Boolean attributes follow HTML presence semantics: present means true, absent means false, and disabled="false" still means disabled.
Attributes
| Attribute | Property | Type | Default | Notes |
|---|---|---|---|---|
auto-start | autoStart | boolean | false | Presence semantics: present means true, absent means false. |
char-duration | charDuration | number | 75 | Kebab-case attribute; camelCase property. |
sound-enabled | soundEnabled | boolean | false | Presence semantics: present means true, absent means false. |
sound-volume | soundVolume | number | 0.25 | Kebab-case attribute; camelCase property. |
variant | variant | 'primary' | 'secondary' | 'outline' | 'primary' | Kebab-case attribute; camelCase property. |
Properties
| Property | Type | Default | Notes |
|---|---|---|---|
onClick | — | — | Recorded in the catalog without a top-level type; pinned from source when the row is ported. |
onComplete | () => void | undefined | Property-only: assign from script; not accepted as an attribute. |
Events
| Event | Upstream prop | Payload type | Notes |
|---|---|---|---|
detent-complete | onComplete | () => void | Composed, bubbles; fires only for a user action. |
Slots
| Slot | Notes |
|---|---|
default | Consumer 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.