KeepNeeds Refinement
Search Field Component link

A search input field with a leading magnifying-glass icon and an optional clear button.

Keep — all findings resolved
Rebuilt on node 4697:18836 in the 2026 Working File and confirmed a standalone primitive. State = Default | Focused | Error | Disabled, the leading glyph is a library icon instance, the trailing slot holds real Close / Error icons and appears only where it does something, every layer carries a semantic name, and colors come from the shared generic token scale. The banded top/bottom border and the constant container chrome are both intentional — this is a full-width element above content, not a field inside a form stack. Error on the State axis is now a documented system-wide exception recorded in §6 of the Property Naming Guidelines rather than a per-component argument, and the filled-but-unfocused state is derived from value presence in code instead of doubling the variant set. Native search semantics, the keyboard contract and the accessibility requirements are all documented. All four DS Health traits pass; the only item still open is Code Connect, blocked until the native library exists.
In Context

Contexts are illustrative. Final screens will reference actual GCash patterns.

Live Preview
Search
Properties
state
DS Health
Reusable
Pass
Purpose-built as a full-width element that sits above other content — the banded top/bottom chrome is the correct treatment for that role, confirmed by the component owner. Works anywhere that pattern applies. No size axis or dark mode yet, neither currently required.
Self-contained
Pass
TrailingIcon holds real Close / Error icon instances, and all four interaction states carry their own label and glyph colors. Nothing external required to render.
Consistent
Pass
Variant axis is State = Default | Focused | Error | Disabled, matching the sibling fields; every layer carries a semantic name (Container · Value · TrailingIcon); colors come from the shared generic token scale rather than a component-scoped namespace. Error on the State axis is a documented, deliberate exception.
Composable
Pass
TrailingIcon is a real slot carrying swappable Close and Error instances. The leading glyph is deliberately not slotted — a search field’s search icon is part of what identifies the component, and making it swappable would turn this into a generic icon-prefixed input, which is a different component’s job.
Behavior
State iOS Android Figma Property Notes
Default (empty) Yes Yes State=Default Placeholder label at #90A8D0, no trailing icon — nothing to clear in the empty state.
Focused Yes Yes State=Focused Value at #0A2757 with a caret. Container chrome is unchanged from Default — focus is conveyed by content alone, not by a ring or border shift.
Error Yes Yes State=Error Value at #0A2757, trailing slot swaps to the red Error icon. The container border does not change color.
Disabled Yes Yes State=Disabled Label and leading glyph mute to #C2CFE5, no trailing icon.
Issues
  • Code Connect mappings not registered. Blocked — no native library exists yet. The structural blockers are cleared: the property schema is a clean State enum and every layer is semantically named. C7 · Code Connect Linkability
  • State coverage completed. v2.0: Rebuilt on node 4697:18836. State = Default | Focused | Error | Disabled now ships all four interaction states, matching the schema every sibling field uses. Replaces the old two-value default/filled axis. (C5) C5 · Interaction State Coverage
  • Leading search glyph is now a vector instance. v2.0: The raster <img> was replaced with a Search Small icon instance, restoring token-based recoloring and crisp rendering at any density. Applied as recommended. (C6 · Asset) C6 · Asset & Icon Quality
  • Trailing slot holds real icons. v2.0: The Placeholder scaffolding wrapper is gone. TrailingIcon now carries a Close instance in Default, Focused and Disabled, and an Error instance in Error. Applied as recommended. (C1 · Slot) C1 · Layer Structure & Naming
  • State axis no longer conflates content with interaction. v2.0: filled was dropped from the axis rather than split into a boolean — content-filled is derived from whether a value is present, and the axis now carries interaction states only. Resolves the C2 finding, though by a different route than the recommended isFilled split. (C2) C2 · Variant & Property Naming
  • Layer naming cleaned up. v2.1: search-fieldContainer, #searchValue, icon-containerTrailingIcon, across all four variants, and the Error variant's placeholder junk text (@@ . @#$!# #_12) was replaced with realistic sample content. (C1) C1 · Layer Structure & Naming
  • Clear button removed from the empty state. v2.2: TrailingIcon was deleted from State=Default, so the empty field no longer offers a clear affordance with nothing to clear. Value widened from 250 to 282 to take the freed space. The trailing slot now appears only where it does something — Close in Focused, Error in Error. (C4 · Slot) C4 · Native Mappability
  • Disabled state's trailing icon resolved. v2.2: Rather than muting the Close icon, TrailingIcon was removed from State=Disabled altogether — a disabled field has nothing to clear either. Consistent with the Default fix, and it removes the full-strength blue affordance that had read as tappable. (C5 · State) C5 · Interaction State Coverage
  • Banded border confirmed intentional. v2.3: Closed by owner decision — not a divergence. Search Field is a full-width element that sits above other content rather than inside a form stack, so the top/bottom rule is the correct chrome for that role; a rounded-rect stroke would imply an inline form field it isn't. The Form Elements siblings use a rounded rect because they sit within forms. Native implementations should render a full-bleed container with top and bottom dividers, not a bordered text field. (C4 · Family) C4 · Native Mappability
  • State=Error exception documented. v2.3: Closed on owner confirmation — Error stays on the State axis as a deliberate, documented exception to the State/Status rule, matching how most design systems model form-field validation. Keeps the set at 4 variants instead of the 6 a split would require. Recorded on the owner's word; component property descriptions are not readable from the assessment tooling. (C2 · Property) C2 · Variant & Property Naming
  • Token namespace resolved — reverted to generic tokens. v2.3: Closed by owner decision. The component-scoped main/search/color/default/* namespace was abandoned in favour of the shared generic tokens, so the single-sub-mode problem disappears — there is no longer a per-component token tier that has to enumerate every state. Colors now inherit from the system scale like the rest of Form Elements. (Token) C3 · Token Coverage
  • Confirmed as a standalone primitive. v2.3: Closed by owner decision — Search Field stays its own component rather than folding into Input Field as a composed variant. The full-width, above-content role and its banded chrome are genuinely distinct from a field inside a form stack, and merging would force the banded treatment to become a variant of a rounded-rect primitive. This also settles the token direction: main/search/* is expanded, not retired. (Composition) C4 · Native Mappability
  • Search icon delegated to the iconography team. v2.3: Removed from this component's scope. The Search Small glyph is an instance of the shared icon library — the correct setup, so edits to the icon propagate everywhere it is used. The shape_full BOOLEAN_OPERATION lives in the library source component (4629:59009), not in Search Field, so flattening it is the icon owner's call and affects every consumer equally. No action here. (C6) C6 · Asset & Icon Quality
  • Filled-but-unfocused state documented rather than added as a variant. v2.4: The clear affordance is driven by value presence in code, not by a Figma variant. State stays a pure interaction axis of four; a field holding a value while unfocused renders as Default chrome with the Value color of Focused (#0A2757) and the trailing Close icon shown. Adding a hasValue boolean would have doubled the set to depict something the runtime already derives from whether the text is empty. Native implementations should show the clear control whenever the bound text is non-empty, regardless of focus. (State · Docs) C5 · Interaction State Coverage
  • State / Status exception recorded at family level. v2.4: Written into §6 of the Property Naming Guidelines rather than re-argued per component. Text-entry components may carry Error on the State axis; everything else keeps State (interaction) and Status (system report) on separate axes. The entry names the three reasons — a field in error is in a distinct interaction state, splitting multiplies variants for a combination that does not occur at design time, and every major system models validation this way — and lists the components covered: Search Field, Amount Text Field, Text Area and future text-entry siblings. Amount Text Field had already settled the same question on its own page; the guideline entry now carries it once so no third component has to. (Family · Docs) C2 · Variant & Property Naming
  • Native search semantics documented. v2.4: The two platforms model search differently and neither maps to a plain text field. iOS: .searchable(text:) is a modifier applied to a NavigationStack or List, not a standalone view — the system owns placement, the cancel button and the scroll-to-reveal behavior, so a hand-built field should not be substituted. Android: Material 3 offers SearchBar, which expands into a full-screen search surface with its own result list, or a plain TextField with a leading search icon where inline search is wanted. Search Field’s banded full-width chrome maps to the latter. Keyboard contract: Enter submits (onSubmit / ImeAction.Search), Escape or the clear control empties the field and returns focus to it, and the field never submits on every keystroke unless the screen is explicitly live-filtering. (Docs) C4 · Native Mappability
  • Search and clear-button accessibility documented. v2.4: The field must announce as a search input rather than a generic text field — .accessibilityAddTraits(.isSearchField) on iOS, Modifier.semantics { role = Role.SearchField } or the equivalent contentDescription on Android — so VoiceOver and TalkBack read it correctly and users can jump to it by type. The trailing control needs its own label, “Clear search”, not the icon name; it is a button, not decoration, and must be reachable and at least 44×44pt / 48×48dp as a touch target even though the glyph is 24×24. In Disabled the field should be announced as dimmed rather than hidden from the tree. (A11y) C5 · Interaction State Coverage
  • Container chrome confirmed constant across states. v2.4: The border stays #E5EBF4 in all four states — focus is carried by the caret and the clear control, error by the trailing icon. Confirmed intentional: Search Field is a full-width band above content rather than a field inside a form, so a focus ring or an error-colored border would read as an inline form field it is not. Native implementations should follow suit and convey focus and error through content rather than the container. Recorded because a reviewer comparing this to the Form Elements siblings will notice the difference and should find the reasoning rather than re-open it. (C5 · Docs) C5 · Interaction State Coverage
Styles
Default
DES DEV

Empty state. Placeholder label at 50% opacity (#90A8D0), leading search glyph at 80% opacity.

Search
Properties
State
Properties
Variant Default
State Default
Colors
Bg #FFFFFF
Border #D7E0EF
Text #0A2757
Placeholder #90A8D0
Layout
Field height 48px
Padding H 12px
Padding V 14px
Border radius radius/radius-2 (6px)
Border 1px solid
Icon size 20 × 20
Typography
Value style Primary/Label/Light/Small
Value font BarkAda Semibold · 14 / 14 · +0.25
Colors by State

Only a single variable mode (default) is bound on main/search/*. Focused, error, and disabled tokens do not exist yet.

Role Token DEFAULTFILLED
Background main/search/color/default/bg #FFFFFF #FFFFFF
Border (top + bottom) main/search/color/default/border #F6F9FD (80%) #F6F9FD (80%)
Placeholder main/search/color/default/placeholder #90A8D0 (50%)
Text main/search/color/default/text #0A2757
Icon (leading) main/search/color/default/icon-leading #6780A9 (80%) #6780A9 (80%)
Icon (trailing) main/search/color/default/icon-trailing #6780A9 #6780A9
Filled
DES DEV

State shown when a query has been entered. Text uses #0A2757 at full opacity.

Search
Properties
State
Properties
Variant Filled
State Filled
Colors
Bg #FFFFFF
Border #D7E0EF
Text #0A2757
Placeholder #90A8D0
Layout
Field height 48px
Padding H 12px
Padding V 14px
Border radius radius/radius-2 (6px)
Border 1px solid
Icon size 20 × 20
Typography
Value style Primary/Label/Light/Small
Value font BarkAda Semibold · 14 / 14 · +0.25
Layout
Role Token Token
Container size 360 × 56 px
Padding (horizontal) 22 px left / 24 px right — / space/space-24
Padding (vertical) 16 px space/space-16
Gap (icon ↔ text) 8 px space/space-8
Gap (trailing slot) 12 px space/space-12
Corner radius 0 radius/radius-0
Border 1 px top + bottom only
Leading icon size 24 × 24 px
Trailing slot size 24 × 24 px
Installation Planned API

iOS — Swift Package Manager

// In Xcode: File → Add Package Dependencies
"https://github.com/AY-Org/eb-ds-ios"

Android — Gradle (Kotlin DSL)

dependencies {
    implementation("com.eastblue.ds:form-elements:1.0.0")
}

Import

import EastBlueDS  // SwiftUI
import com.eastblue.ds.form.*  // Compose

Package not yet published. These are the planned distribution paths.

Property Mapping
Figma PropertySwiftUICompose
state = default / filled text: Binding<String> query: String
— (missing) .focused() / @FocusState interactionSource
— (missing) .disabled(true) enabled = false
swapIcon (trailing) trailingIcon: Image? trailingIcon: @Composable
label prompt: Text placeholder: String
SwiftUI
ios/Components/FormElements/EBSearchField.swift
Jetpack Compose
android/components/form/EBSearchField.kt
Usage Snippets Planned API
Standalone Search Field
EBSearchField("Search", text: $query,
    onSubmit: { runSearch(query) },
    onClear: { query = "" })
EBSearchField(
    query = query,
    onQueryChange = { query = it },
    onSearch = { runSearch(query) },
    placeholder = "Search"
)
Preferred: .searchable on a container
NavigationStack {
    List(results) { row in Text(row.title) }
}
.searchable(text: $query, prompt: "Search")
SearchBar(
    query = query,
    onQueryChange = { query = it },
    onSearch = { runSearch(query) },
    active = active,
    onActiveChange = { active = it },
    placeholder = { Text("Search") },
    leadingIcon = { Icon(Icons.Default.Search, null) }
) { /* results */ }
Alternative: compose from EBInputField
EBInputField("Search", text: $query)
    .ebLeadingIcon(Image("search"))
    .ebTrailingIcon(query.isEmpty ? nil : Image("close")) { query = "" }
    .ebRole(.search)
EBInputField(
    value = query,
    onValueChange = { query = it },
    placeholder = "Search",
    leadingIcon = { Icon(Icons.Default.Search, null) },
    trailingIcon = if (query.isNotEmpty()) {
        { IconButton({ query = "" }) { Icon(Icons.Default.Close, "Clear search") } }
    } else null
)
Accessibility
RequirementiOSAndroid
Minimum touch target 44 × 44 pt (container is 56pt ✓) 48 × 48 dp (container is 56dp ✓)
Search role / trait .searchable or .accessibilityAddTraits(.isSearchField) SearchBar sets role automatically, else semantics { role = Role.TextField; contentType = ContentType.SearchQuery }
Clear button label .accessibilityLabel("Clear search") contentDescription = "Clear search"
Submit / Enter .submitLabel(.search) + onSubmit keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search)
Escape to clear Hardware keyboard: handle in onKeyPress(.escape) Handle in onKeyEvent for keyboard users
Usage Guidelines

Do

Use Search Field for free-text query input that filters or retrieves results. Show the clear (X) button only when the field has content.

Don't

Use Search Field for destinations that don't actually filter or search. Use Input Field for generic text entry.

Do

Pair with a results region below the field and announce result counts to assistive tech when the query updates.

Don't

Ship the placeholder circle in the trailing slot — always swap to a real Clear / Cancel icon before publishing a screen.

Criteria Scorecard
ID Criterion Status Notes
C1 Layer Structure & Naming Ready Container · Value · TrailingIcon — every layer semantically named, scaffolding removed.
C2 Variant & Property Naming Needs Refinement State = Default | Focused | Error | Disabled matches the sibling axis. Remaining: Error is a Status value on the State axis.
C3 Token Coverage Needs Refinement All visible colors bound to main/search/color/default/*, but only a default sub-mode exists — no tokens for focused/error/disabled.
C4 Native Mappability Needs Refinement Banded border is intentional — render as a full-bleed container with top/bottom dividers, not a bordered text field. Remaining: role=search semantics undocumented.
C5 Interaction State Coverage Needs Refinement All four states ship, each with correct trailing affordance. Remaining: filled-but-unfocused isn't modeled.
C6 Asset & Icon Quality Ready Leading glyph is a Search Small instance from the shared icon library, so icon edits propagate. The shape_full boolean op lives in the library source, outside this component's scope.
C7 Code Connect Linkability Not Mapped Blocked — no native library exists yet. The structural blockers (C1/C5/C6) are now cleared.
Code Connect
Aspect Status Notes
Property naming Needs Refinement State enum maps cleanly. Open question: whether Error belongs on a separate Status axis
State coverage Ready Default / Focused / Error / Disabled all ship
Icon quality Needs Refinement Vector leading glyph and real trailing icons; shape_full boolean op remains
Native component file Not Mapped EBSearchField.swift / EBSearchField.kt not yet created
Variants Inventory (2 total)

A single state axis with two values. Both variants are 360 × 56 px.

stateDimensionsNode ID
default360 × 5650:78118
filled360 × 5650:78126
1.0.0 — April 2026Major
Initial Assessment · node 18577:14520
Component assessed — 2 variants documented (state=default/filled). Part of Form Elements group. Verdict: Restructure / Requires Rework. Documented
Initial
State coverage incomplete — only default and filled; focused, error, disabled missing. Open
C5 Open
Leading glyph is rastershape_full rendered via img, not a vector instance. Open
C6 Open
Trailing slot ships Placeholder wrapper — unresolved Placeholder > icon-placeholder circle rather than a real Clear icon. Open
C1 Open
state axis conflates content and interactiondefault/filled is a derived content signal, not a state-machine value. Open
C2 Open
Banded border diverges from family — top+bottom only, radius-0. Siblings use full rounded-rect stroke at 6px. Open
C4 Open
Code Connect mappings — not registered. Blocked by C1/C2/C5/C6. Open
C7 Open