A text field for filtering or searching, with a magnifier before the text, a clear control after it and an optional key hint at the end.
Options
| Choice | Values | Default | What it does |
|---|---|---|---|
sizesmbk-search--size-<value> | lgmdsm | md | lg is 48 tall with larger text, for a search that leads a page; md and sm are the input’s two heights, with their padding, icons and text. Small text never goes under 16 px on a phone. |
errorsmbk-search--error | On or off | Off | The query is invalid. Draws the danger border, kept while focused, and the error icon. |
Examples
Usage
The input well with a magnifier; the clear control appears once there is text.
With a query
The clear control empties the field and puts focus back in it.
With a key hint
A Kbd at the end shows the shortcut that jumps to the field, a space between its keys. It gives way to the clear control once there is text.
Error
The danger border and the warning icon say the query cannot be searched.
Read only
Flat on the page, with no clear control and no key hint.
Disabled
Dimmed and out of reach; the key hint fades too.
Guidelines
Purpose
A text field whose value filters or searches something. It looks like the input and adds the two things a search needs: a magnifier that says what the field is for, and a clear control that empties it in one press. It can also show the key that jumps to it.
When to use
- Filtering a list, a table or a sidebar on the page.
- A site search box.
When not to use
Usage rules
Do
- Show results or filter as the person types when the set is small; wait for Enter when a search is expensive.
- Show the key hint only on the one search field the shortcut reaches.
Don't
- Put a search field inside a form that submits on Enter unless that is what searching does.
Show the key hint on the one search field the shortcut reaches, so the hint is true.
The same hint on two fields promises a shortcut that can only reach one of them.
Anatomy
- The block, always on the input's well: its markup carries the input's classes as well as its own.
- The magnifier, the input's icon part, always present.
- The control part, a native search control, which is also the input's control part.
- In error, the input's error icon, a warning sign after the query.
- The clear part, a cross button at the field's icon size inside a box the size of the least target a press needs, shown when there is something to clear.
- The kbd part, an optional small Kbd at the far end naming the key that jumps to the field, shown while the field is empty.
Behavior
Typing filters or searches, as the page decides. The clear control shows only while the field holds text, and the key hint only while it is empty, so the end of the field holds one of them at a time; pressing the clear control empties the field, calls back, and puts focus back in the field. Escape empties a field holding text in every browser, Safari too, and the page hears it as a change; in an empty field Escape is left to the page. The padding, the icon sizes, hover, focus, error, read-only, disabled and the md and sm sizes are the input's own, because the field is built on the input; a change to the input changes the search field too. The large size is the search field's own: 48 tall, with larger text and wider side padding, for a search that leads a page; its icons, clear control and key hint stay the medium field's. A read-only field shows neither the clear control nor the key hint, and Escape leaves it as it is. A disabled field fades its key hint with the media disabled opacity.
The key hint shows whatever the shortcut is, with a space between keys, e.g. "⌘ K". It is only a picture of the keys: the page that shows it must also wire the shortcut, or the hint is a promise the field does not keep.
The clear control is offered only when the field is given onClear. A field whose value the page does not control is emptied by the component when cleared; a field whose value the page controls must be emptied by the page in onClear. onChange passes the browser's own event, as the native field does.
Accessibility
What it does for you
- In development, a search field with no name logs a warning in the console.
- The clear control is a real button with an accessible name, "Clear search", and a box of 24 by 24 px, the least a press needs. Pressing it empties the field and puts focus back in the field.
- The browser's own clear control is hidden so there is one, styled, in the same place in every browser.
- The key hint is hidden from assistive technology.
- Escape empties a field holding text in every browser, Safari too, and the page hears it as a change.
What you must do
- Give it a label like any input; a search landmark around it helps when it is the page's search.
- Change the clear control's name with
clearLabelfor a page in another language. - When the page wires the shortcut, name it on the control with
aria-keyshortcuts, which is where a screen reader looks for it. The hint alone is only a picture of the keys.
How to test
- Type in the field: the clear control appears at its end. Tab to it and press Enter or Space: the field empties and focus is back in it.
- Press Escape in a field holding text: it empties. In an empty field, Escape is left to the page.
- With a screen reader, the field is read with its label as a search field, and the clear control as the button "Clear search". The key hint is not read.
- With text at 200% and the page 320 pixels wide, the text and the clear control stay inside the field.
- In a forced-color mode, the focus border and the text stay visible.
Keyboard
| Key | What it does |
|---|---|
| Tab | Moves focus to the field, then to the clear control when it shows. |
| Escape | Empties a field holding text. In an empty or read-only field, it is left to the page. |
| EnterorSpace | On the clear control, empties the field and puts focus back in it. |
Source
The files the search field is made of. Its folder on GitHub (opens in a new tab) holds them with its wrapper and its document.
Stylesheet
/* smbk-search — Search field. Figma "Search Field" 4212:26902.
* Built on the input: the block always sits on an smbk-input well, and the
* magnifier is the input's icon part. This stylesheet adds only what a search
* needs: a clear control, an optional key hint and the large size. The
* padding, the icon sizes, hover, focus, error, read-only, disabled and the
* small size are the input's own, so a change to the input reaches the
* search field. */
.smbk-search__control {
appearance: none;
}
.smbk-search__control::-webkit-search-cancel-button,
.smbk-search__control::-webkit-search-decoration {
appearance: none;
}
/* The cross is the field's icon size, inside a 24 px box, the least a press needs; the box reaches past
the cross into the gap and the padding, so the cross sits where Figma draws it. */
.smbk-search__clear {
box-sizing: border-box;
display: inline-flex;
flex-shrink: 0;
align-items: center;
justify-content: center;
width: var(--smbk-size-target-min);
height: var(--smbk-size-target-min);
margin: calc((var(--smbk-size-icon-md) - var(--smbk-size-target-min)) / 2);
padding: 0;
border: 0;
background: none;
color: var(--smbk-color-text-subtlest);
cursor: pointer;
appearance: none;
-webkit-tap-highlight-color: transparent;
touch-action: manipulation;
}
.smbk-search__clear > .smbk-icon {
font-size: var(--smbk-size-icon-md);
}
.smbk-input--size-sm .smbk-search__clear {
margin: calc((var(--smbk-size-icon-sm) - var(--smbk-size-target-min)) / 2);
}
.smbk-input--size-sm .smbk-search__clear > .smbk-icon {
font-size: var(--smbk-size-icon-sm);
}
.smbk-search__clear:focus-visible {
outline: var(--smbk-focus-ring-width) solid var(--smbk-color-border-focus);
outline-offset: 0;
border-radius: var(--smbk-radius-full);
}
.smbk-search:has(.smbk-input__control:disabled) .smbk-search__clear {
color: var(--smbk-color-text-disabled);
pointer-events: none;
}
/* ---- large (lg): 48 tall, Body LG, a wider side padding; the icons, the gap, the corner, the clear
box and the key hint stay the md field's. Two classes, so it wins over the input's md rules
whichever stylesheet loads first. ---- */
.smbk-input.smbk-search--size-lg {
height: var(--smbk-size-control-lg);
padding: 0 var(--smbk-space-inset-xl);
font-family: var(--smbk-typography-body-lg-font-family);
font-size: var(--smbk-typography-body-lg-font-size);
font-weight: var(--smbk-typography-body-lg-font-weight);
line-height: var(--smbk-typography-body-lg-line-height);
letter-spacing: var(--smbk-typography-body-lg-letter-spacing);
}
/* The key hint is a small Kbd at the far end. */
.smbk-search__kbd {
flex-shrink: 0;
}
.smbk-search:has(.smbk-input__control:disabled) .smbk-search__kbd {
opacity: var(--smbk-opacity-disabled);
}
/* ---- touch: the clear button takes an invisible press area ----
On a touch screen an invisible area, centred on it, takes the press out to the comfortable target in each direction
it falls short of it; its look, its focus ring and its place stay as drawn. */
@media (pointer: coarse) {
.smbk-search__clear {
position: relative;
}
.smbk-search__clear::after {
content: '';
position: absolute;
inset: min(0%, (100% - var(--smbk-size-target-comfortable)) / 2);
}
/* Right after the value, halfway into the gap: the well's gap less the margin the cross gives back round its icon. */
.smbk-input > :is(.smbk-input__control, .smbk-combobox__values) + .smbk-search__clear::after {
inset-inline-start: max(min(0%, (100% - var(--smbk-size-target-comfortable)) / 2), (var(--smbk-space-inline-md) + (var(--smbk-size-icon-md) - var(--smbk-size-target-min)) / 2) / -2);
}
.smbk-input--size-sm > :is(.smbk-input__control, .smbk-combobox__values) + .smbk-search__clear::after {
inset-inline-start: max(min(0%, (100% - var(--smbk-size-target-comfortable)) / 2), (var(--smbk-space-inline-sm) + (var(--smbk-size-icon-sm) - var(--smbk-size-target-min)) / 2) / -2);
}
}Tokens it reads
| Token and CSS | Value | Preview | Foundation |
|---|---|---|---|
| size.target.min--smbk-size-target-min | 24px | Size | |
| size.icon.md--smbk-size-icon-md | 20px | Size | |
| color.text.subtlest--smbk-color-text-subtlest | light: #606a75dark: #7c8692changes with theme | Color | |
| size.icon.sm--smbk-size-icon-sm | 16px | Size | |
| focus.ring.width--smbk-focus-ring-width | 2px | Focus | |
| color.border.focus--smbk-color-border-focus | light: #cc0d0ddark: #f34336changes with theme | Color | |
| radius.full--smbk-radius-full | 9999px | Radius | |
| color.text.disabled--smbk-color-text-disabled | light: #7c8692dark: #606a75changes with theme | Color | |
| size.control.lg--smbk-size-control-lg | comfortable: 48pxcompact: 40pxchanges with density | Size | |
| space.inset.xl--smbk-space-inset-xl | comfortable: 16pxcompact: 12pxchanges with density | Spacing | |
| typography.body-lg--smbk-typography-body-lg-font-family--smbk-typography-body-lg-font-size--smbk-typography-body-lg-font-weight--smbk-typography-body-lg-line-height--smbk-typography-body-lg-letter-spacing--smbk-typography-body-lg-text-transform | fontFamily: Geist, Geist Fallback, system-ui, sans-seriffontSize: 20pxfontWeight: 400lineHeight: 1.5letterSpacing: 0px | The quick brown fox | Typography |
| opacity.disabled--smbk-opacity-disabled | 0.5 | Opacity | |
| size.target.comfortable--smbk-size-target-comfortable | 44px | Size | |
| space.inline.md--smbk-space-inline-md | comfortable: 8pxcompact: 6pxchanges with density | Spacing | |
| space.inline.sm--smbk-space-inline-sm | comfortable: 6pxcompact: 4pxchanges with density | Spacing |
Manifest
{
"name": "search-field",
"block": "smbk-search",
"summary": "A text field for filtering or searching, with a magnifier before the text, a clear control after it and an optional key hint at the end.",
"adapt": "css",
"axes": {
"size": {
"values": [
"lg",
"md",
"sm"
],
"default": "md",
"description": "lg is 48 tall with larger text, for a search that leads a page; md and sm are the input’s two heights, with their padding, icons and text. Small text never goes under 16 px on a phone."
}
},
"flags": {
"error": {
"description": "The query is invalid. Draws the danger border, kept while focused, and the error icon."
}
},
"parts": [
"control",
"clear",
"kbd"
],
"figmaNodeId": "4212:26902"
}