Detent web components
By Shane Logsdon

Buttons & actions

Button

Implemented

Primary, secondary, outline, ghost and destructive actions

detent-button

Port status
Catalog status
implemented
Origin
upstream
Phase
1
Wave
phase-1-pilot
Group
Buttons & actions
Primary

4.5 KB gzip for the class module

Variants

Secondary Outline Ghost Destructive Success Link Gradient Small Large Save Disabled

Recorded states

Disabled
disabled
Save
loading
  • focus-visible recorded state; no setter
  • pressed recorded state; no setter
  • reduced motion recorded state; no setter

Usage

Import

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

// or register it yourself
import { DetentButton } from '/components/button/button.js';

Attributes

  • disabled
  • full-width
  • is-loading
  • loading-text
  • size
  • variant

Properties

No properties are recorded for this component.

Slots

  • default
  • left-icon
  • right-icon

Events

No events are recorded for this component.

Example

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

<detent-button
  disabled
  full-width
  is-loading
  loading-text
  size="md"
  variant="primary"
></detent-button>

Source

The pinned upstream source for this row:

  • src/components/ui/Button.tsx sha256 9ea5b0730067…

Native files

The native module and its artifacts:

  • src/components/button/button.ts
  • src/components/button/styles.css
  • src/components/button/define.ts
  • docs/components/button.md
  • site/examples/button.ts
  • tests/browser/button.spec.ts
  • evidence/components/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.
full-widthfullWidthbooleanfalsePresence semantics: present means true, absent means false.
is-loadingisLoadingbooleanfalsePresence semantics: present means true, absent means false.
loading-textloadingTextstringundefinedKebab-case attribute; camelCase property.
sizesize'sm' | 'md' | 'lg' | 'icon''md'Kebab-case attribute; camelCase property.
variantvariant'primary' | 'secondary' | 'outline' | 'ghost' | 'destructive' | 'success' | 'link' | 'gradient''primary'Kebab-case attribute; camelCase property.

Properties

No property-only values are recorded for this component.

Events

No custom events are recorded for this component.

Slots

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

CSS customisation

  • React `children`/`ReactNode` props become DOM slots; caller content stays light DOM.
  • 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).
  • React state/hooks replaced by element-owned state with pre-upgrade property preservation.
  • Native form surface added by the Phase 1 plan: type (button|submit|reset, default button preserving upstream), name, value, static formAssociated, forwarded submit/reset to the owning form or form="<id>". Not recorded upstream.
  • type=submit forwards through HTMLFormElement.prototype.requestSubmit with a transient hidden native submit button carrying the submitter name/value, because requestSubmit(host) throws TypeError for a form-associated custom element on Chromium, Firefox and WebKit (F-3); event.submitter in a submit listener is that proxy, and an detent-button is never the form's implicit submit button.
  • variant="default" from the upstream source type union is not in the catalogued variant list; it resolves to primary (visually identical) with one development console.warn per distinct value.
  • disabled="false" disables (HTML presence semantics); variant/size/type reflect their resolved value, are rewritten on invalid input, and warn once per distinct invalid value; name/value reflect because the entry name is read from the content attribute.
  • No custom events: the native composed click is used (one per activation), host.click() activates the action like button.click(), and programmatic property assignment emits nothing.
  • Framer Motion whileTap scale 0.97 is a CSS :active transform with no transition under prefers-reduced-motion: reduce.
  • Tinted destructive and success variants mix the hue with the theme surface and text tokens; the pinned upstream hex pair measured 3.73:1 on a light surface (WCAG AA needs 4.5:1). Hover fills are solid 700-level hues so white stays at least 6.29:1.

What it does

A form-aware button with eight visual variants, four sizes plus an icon size, a loading state and optional leading or trailing icons. It submits, resets and validates like a native button.

Upstream behaviour
  • 8 visual variants: Primary, Secondary, Outline, Ghost, Destructive, Success, Link, Gradient
  • 4 size dimensions: Small (sm), Medium (md), Large (lg), and square Icon
  • Spring tap micro-interaction (whileTap 0.97) via Framer Motion
  • Accessible loading state with integrated monochrome spinner & aria-busy
  • Left and right icon slots with automatic sizing and gap alignment
  • Full width layout support (fullWidth)
  • Strictly adheres to EasyUI monochrome dark palette and sky focus ring
Differences from upstream
  • React `children`/`ReactNode` props become DOM slots; caller content stays light DOM.
  • 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).
  • React state/hooks replaced by element-owned state with pre-upgrade property preservation.
  • Native form surface added by the Phase 1 plan: type (button|submit|reset, default button preserving upstream), name, value, static formAssociated, forwarded submit/reset to the owning form or form="<id>". Not recorded upstream.
  • type=submit forwards through HTMLFormElement.prototype.requestSubmit with a transient hidden native submit button carrying the submitter name/value, because requestSubmit(host) throws TypeError for a form-associated custom element on Chromium, Firefox and WebKit (F-3); event.submitter in a submit listener is that proxy, and an detent-button is never the form's implicit submit button.
  • variant="default" from the upstream source type union is not in the catalogued variant list; it resolves to primary (visually identical) with one development console.warn per distinct value.
  • disabled="false" disables (HTML presence semantics); variant/size/type reflect their resolved value, are rewritten on invalid input, and warn once per distinct invalid value; name/value reflect because the entry name is read from the content attribute.
  • No custom events: the native composed click is used (one per activation), host.click() activates the action like button.click(), and programmatic property assignment emits nothing.
  • Framer Motion whileTap scale 0.97 is a CSS :active transform with no transition under prefers-reduced-motion: reduce.
  • Tinted destructive and success variants mix the hue with the theme surface and text tokens; the pinned upstream hex pair measured 3.73:1 on a light surface (WCAG AA needs 4.5:1). Hover fills are solid 700-level hues so white stays at least 6.29:1.
Previous: Breadcrumb Next: Calendar

Docs

Components

Project