Detent web components
By Shane Logsdon

Forms & inputs

Form

Implemented

Composable, accessible form primitives and controls

detent-form

Port status
Catalog status
implemented
Origin
upstream
Phase
3
Wave
phase-3-forms
Group
Forms & inputs
Email address Continue

5.4 KB gzip for the class module

Variants

Password Team address Locked field

Recorded states

  • focus-visible recorded state; no setter
  • checked recorded state; no setter

Usage

Import

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

// or register it yourself
import { DetentForm } from '/components/form/form.js';

Attributes

  • default-value
  • description
  • disabled
  • error
  • name
  • required
  • show-password-toggle
  • type
  • value

Properties

  • error
  • onChange
  • onSubmit

Slots

  • label
  • left-icon
  • right-icon

Events

  • detent-change — { value: string }
  • detent-submit — {}

Example

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

<detent-form
  default-value
  description
  disabled
  error
  name
  required
  show-password-toggle
  type
  value
></detent-form>

Property-only values

const element = document.querySelector('detent-form');
if (element !== null) {
  // onSubmit: (e: FormEvent) => void
  element.onSubmit = () => {};
  // error: string | boolean
  element.error = undefined;
}

Source

The pinned upstream source for this row:

  • src/components/ui/Form.tsx sha256 32b9aa0b5e4f…

Native files

The native module and its artifacts:

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

API

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

Attributes

Attributes
AttributePropertyTypeDefaultNotes
default-value———Recorded in the catalog as an attribute.
description———Recorded in the catalog as an attribute.
disabled———Recorded in the catalog as an attribute.
errorerrorstring | booleanundefinedKebab-case attribute; camelCase property.
name———Recorded in the catalog as an attribute.
requiredrequiredbooleanfalsePresence semantics: present means true, absent means false.
show-password-toggleshowPasswordTogglebooleanfalsePresence semantics: present means true, absent means false.
type———Recorded in the catalog as an attribute.
value———Recorded in the catalog as an attribute.

Properties

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

Events

Events
EventUpstream propPayload typeNotes
detent-changeonChange(value: string) => voidComposed, bubbles; fires only for a user action.
detent-submitonSubmit(event: FormEvent) => voidComposed, bubbles; fires only for a user action.

Slots

Slots
SlotNotes
labelConsumer 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.
  • 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.
  • `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.
  • One `detent-form` tag implements the row's documented Input surface plus the Form root's submit relay; the twelve sibling exports (FormItem, FormLabel, FormControl, FormDescription, FormMessage, Textarea, Select, Checkbox, RadioGroup, RadioGroupItem, Switch) are not shipped, because PC-2.1 is one-to-one and the pinned source declares no variant enum to select among them.
  • Native additions: type, name, value, default-value, required, disabled, setFormValue/setValidity, checkValidity/reportValidity, submit(), reset(), formDisabledCallback/formResetCallback/formStateRestoreCallback.
  • `detent-submit` detail is `{}` and the event is cancelable: `preventDefault()` is honoured on the native submit, because a React FormEvent cannot become plain detail (PC-6.1/6.3/6.4).
  • A shadow-DOM `<form>` cannot own light-DOM controls, so `detent-form` is a field inside the consumer's own `<form>` and relays its submission (PC-7.9); no noValidate wrapper is supplied.
  • The password toggle is tabbable (upstream used tabIndex={-1}) and carries aria-pressed plus Show/Hide password names.
  • The Framer Motion springSnappy entrance is CSS transitions (PC-10.4), removed under prefers-reduced-motion.
  • Error and danger colours are token color-mix values, not the upstream dark-only rose-500/50.

What it does

A form container that validates its fields, focuses the first invalid control on submit and surfaces per-field errors.

Upstream behaviour
  • Modular layout primitives: Form, FormItem, FormLabel, FormControl, FormDescription, FormMessage
  • Comprehensive controls: Input, Textarea, Select, Checkbox, RadioGroup, Switch
  • Spring-animated validation errors and password visibility toggle
  • Tactile check, radio dot, and toggle switch spring physics
  • Accessible ARIA semantics, required asterisks, and keyboard navigation
  • Strict monochrome dark styling matching EasyUI surface elevation tokens
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.
  • `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.
  • One `detent-form` tag implements the row's documented Input surface plus the Form root's submit relay; the twelve sibling exports (FormItem, FormLabel, FormControl, FormDescription, FormMessage, Textarea, Select, Checkbox, RadioGroup, RadioGroupItem, Switch) are not shipped, because PC-2.1 is one-to-one and the pinned source declares no variant enum to select among them.
  • Native additions: type, name, value, default-value, required, disabled, setFormValue/setValidity, checkValidity/reportValidity, submit(), reset(), formDisabledCallback/formResetCallback/formStateRestoreCallback.
  • `detent-submit` detail is `{}` and the event is cancelable: `preventDefault()` is honoured on the native submit, because a React FormEvent cannot become plain detail (PC-6.1/6.3/6.4).
  • A shadow-DOM `<form>` cannot own light-DOM controls, so `detent-form` is a field inside the consumer's own `<form>` and relays its submission (PC-7.9); no noValidate wrapper is supplied.
  • The password toggle is tabbable (upstream used tabIndex={-1}) and carries aria-pressed plus Show/Hide password names.
  • The Framer Motion springSnappy entrance is CSS transitions (PC-10.4), removed under prefers-reduced-motion.
  • Error and danger colours are token color-mix values, not the upstream dark-only rose-500/50.
Previous: Focus Mode Next: Funnel Chart

Docs

Components

Project