Detent web components
By Shane Logsdon

Navigation

Expanding Search Field

Implemented

Search field that expands from an icon

detent-expanding-search-field

Port status
Catalog status
implemented
Origin
upstream
Phase
3
Wave
phase-3-forms
Group
Navigation

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

Variants

expanded (value)
custom widths
disabled

No interaction yet.

Recorded states

  • expanded recorded state; no setter
  • collapsed recorded state; no setter

Usage

Import

import '/components/expanding-search-field/define.js';

// or register it yourself
import { DetentExpandingSearchField } from '/components/expanding-search-field/expanding-search-field.js';

Attributes

  • aria-label
  • auto-focus
  • collapsed-width
  • disabled
  • expanded-width
  • name
  • placeholder
  • required
  • value

Properties

  • value
  • placeholder
  • collapsedWidth
  • expandedWidth
  • disabled
  • autoFocus
  • name
  • required
  • expanded
  • onChange
  • onSubmit
  • onClear

Slots

No slots are recorded for this component.

Events

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

Example

Runnable example: site/examples/expanding-search-field.ts. The snippet below is generated from the catalogued attributes.

<detent-expanding-search-field
  aria-label
  auto-focus
  collapsed-width="220px"
  disabled
  expanded-width="340px"
  name
  placeholder="Search..."
  required
  value
></detent-expanding-search-field>

Property-only values

const element = document.querySelector('detent-expanding-search-field');
if (element !== null) {
  // onChange: (value: string) => void
  element.onChange = () => {};
  // onSubmit: (value: string) => void
  element.onSubmit = () => {};
  // onClear: () => void
  element.onClear = () => {};
}

Source

The pinned upstream source for this row:

  • src/components/ui/IOSSearchBar.tsx sha256 ccc3631956ae…

Native files

The native module and its artifacts:

  • src/components/expanding-search-field/expanding-search-field.ts
  • src/components/expanding-search-field/styles.css
  • src/components/expanding-search-field/define.ts
  • docs/components/expanding-search-field.md
  • site/examples/expanding-search-field.ts
  • tests/browser/expanding-search-field.spec.ts
  • evidence/components/expanding-search-field.md

API

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

Attributes

Attributes
AttributePropertyTypeDefaultNotes
aria-label———Recorded in the catalog as an attribute.
auto-focus———Recorded in the catalog as an attribute.
collapsed-widthcollapsedWidthnumber | string'220px'Kebab-case attribute; camelCase property.
disabled———Recorded in the catalog as an attribute.
expanded-widthexpandedWidthnumber | string'340px'Kebab-case attribute; camelCase property.
name———Recorded in the catalog as an attribute.
placeholderplaceholderstring'Search...'Kebab-case attribute; camelCase property.
required———Recorded in the catalog as an attribute.
valuevaluestring—Kebab-case attribute; camelCase property.

Properties

Properties
PropertyTypeDefaultNotes
valuestring—Property-only: assign from script; not accepted as an attribute.
placeholderstring'Search...'Property-only: assign from script; not accepted as an attribute.
collapsedWidthnumber | string'220px'Property-only: assign from script; not accepted as an attribute.
expandedWidthnumber | string'340px'Property-only: assign from script; not accepted as an attribute.
disabled——Recorded in the catalog without a top-level type; pinned from source when the row is ported.
autoFocus——Recorded in the catalog without a top-level type; pinned from source when the row is ported.
name——Recorded in the catalog without a top-level type; pinned from source when the row is ported.
required——Recorded in the catalog without a top-level type; pinned from source when the row is ported.
expanded——Recorded in the catalog without a top-level type; pinned from source when the row is ported.
onChange(value: string) => void—Property-only: assign from script; not accepted as an attribute.
onSubmit(value: string) => voidundefinedProperty-only: assign from script; not accepted as an attribute.
onClear() => 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(value: string) => voidComposed, bubbles; fires only for a user action.
detent-clearonClear() => voidComposed, bubbles; fires only for a user action.

Slots

No slots are recorded for this component.

CSS customisation

  • 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.
  • Controlled/uncontrolled React pair maps to initial-value attributes plus owned state and change events.
  • Escape clears a non-empty value first and collapses only on the second press (upstream blurred immediately).
  • One clear affordance: a single `<button aria-label="Clear search">` with the native `::-webkit-search-cancel-button` suppressed, so the action is never offered or tabbed twice.
  • The ⌘K hint is presentation only (aria-hidden, no document-level shortcut registered).
  • The Framer Motion width spring becomes the shared spring helper with the pinned springSmooth 200/28/0.85. The clear button pops in and out (opacity 0/scale 0.8) on upstream's duration-only 0.15 s transition, which framer-motion 13 runs as a keyframes tween with easeInOut (CSS ease-in-out), via WAAPI; it is inert while it leaves.
  • Form participation added; upstream dark-only hex pairs (#0E0E0E/#525252/#6B6B6B) become tokens so light mode is supported and measured.

What it does

A search pill that springs from its collapsed width to its expanded width while focused, with one tactile clear button and a decorative ⌘K hint. One real <input type="search">, one clear control.

Upstream behaviour
  • Smooth spring width expansion on input focus and auto-collapse on blur
  • Conditional quick-clear button with spring scale entrance and exit
  • Built-in keyboard hotkey indicator (⌘K) and Escape key blur support
  • Semantic search input type with full WCAG AA accessibility
Differences from upstream
  • 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.
  • Controlled/uncontrolled React pair maps to initial-value attributes plus owned state and change events.
  • Escape clears a non-empty value first and collapses only on the second press (upstream blurred immediately).
  • One clear affordance: a single `<button aria-label="Clear search">` with the native `::-webkit-search-cancel-button` suppressed, so the action is never offered or tabbed twice.
  • The ⌘K hint is presentation only (aria-hidden, no document-level shortcut registered).
  • The Framer Motion width spring becomes the shared spring helper with the pinned springSmooth 200/28/0.85. The clear button pops in and out (opacity 0/scale 0.8) on upstream's duration-only 0.15 s transition, which framer-motion 13 runs as a keyframes tween with easeInOut (CSS ease-in-out), via WAAPI; it is inert while it leaves.
  • Form participation added; upstream dark-only hex pairs (#0E0E0E/#525252/#6B6B6B) become tokens so light mode is supported and measured.
Previous: Intro Loader Next: Ripple Button

Docs

Components

Project