Navigation
Expanding Search Field
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
No interaction yet.
Recorded states
expandedrecorded state; no settercollapsedrecorded 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-labelauto-focuscollapsed-widthdisabledexpanded-widthnameplaceholderrequiredvalue
Properties
valueplaceholdercollapsedWidthexpandedWidthdisabledautoFocusnamerequiredexpandedonChangeonSubmitonClear
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.tsxsha256 ccc3631956ae…
Native files
The native module and its artifacts:
src/components/expanding-search-field/expanding-search-field.tssrc/components/expanding-search-field/styles.csssrc/components/expanding-search-field/define.tsdocs/components/expanding-search-field.mdsite/examples/expanding-search-field.tstests/browser/expanding-search-field.spec.tsevidence/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
| Attribute | Property | Type | Default | Notes |
|---|---|---|---|---|
aria-label | — | — | — | Recorded in the catalog as an attribute. |
auto-focus | — | — | — | Recorded in the catalog as an attribute. |
collapsed-width | collapsedWidth | number | string | '220px' | Kebab-case attribute; camelCase property. |
disabled | — | — | — | Recorded in the catalog as an attribute. |
expanded-width | expandedWidth | number | string | '340px' | Kebab-case attribute; camelCase property. |
name | — | — | — | Recorded in the catalog as an attribute. |
placeholder | placeholder | string | 'Search...' | Kebab-case attribute; camelCase property. |
required | — | — | — | Recorded in the catalog as an attribute. |
value | value | string | — | Kebab-case attribute; camelCase property. |
Properties
| Property | Type | Default | Notes |
|---|---|---|---|
value | string | — | Property-only: assign from script; not accepted as an attribute. |
placeholder | string | 'Search...' | Property-only: assign from script; not accepted as an attribute. |
collapsedWidth | number | string | '220px' | Property-only: assign from script; not accepted as an attribute. |
expandedWidth | number | 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) => void | undefined | Property-only: assign from script; not accepted as an attribute. |
onClear | () => void | undefined | Property-only: assign from script; not accepted as an attribute. |
Events
| Event | Upstream prop | Payload type | Notes |
|---|---|---|---|
detent-change | onChange | (value: string) => void | Composed, bubbles; fires only for a user action. |
detent-submit | onSubmit | (value: string) => void | Composed, bubbles; fires only for a user action. |
detent-clear | onClear | () => void | Composed, 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.