Components
Suggestion
Suggestion is a searchable "select" with support for multiple selections.
Suggestion is under development. If you find any errors or bugs, please report them on Github or Slack.
HTML
Unable to parse html
const PreviewEn = () => { const DATA_PLACES = ['Sogndal', 'Oslo', 'Brønnøysund']; return ( <Field> <Label>Select a destination</Label> <EXPERIMENTAL_Suggestion> <EXPERIMENTAL_Suggestion.Input /> <EXPERIMENTAL_Suggestion.Clear /> <EXPERIMENTAL_Suggestion.List> <EXPERIMENTAL_Suggestion.Empty> No results found </EXPERIMENTAL_Suggestion.Empty> {DATA_PLACES.map((place) => ( <EXPERIMENTAL_Suggestion.Option key={place} label={place} value={place} > {place} <div>Municipality</div> </EXPERIMENTAL_Suggestion.Option> ))} </EXPERIMENTAL_Suggestion.List> </EXPERIMENTAL_Suggestion> </Field> ); }; render(<PreviewEn />)
Usage
Suggestion is based on open-ui's combobox pattern (open-ui.org) and uses u-combobox from u-elements (github.io) to provide core combobox functionality.
Use the class ds-suggestion on the custom element <ds-suggestion>.
Add <input class="ds-input" /> and <u-datalist> with <u-option> children to get a working suggestion.
For a valid form control, we recommend using <ds-suggestion> inside <ds-field> together with a <label>.
CSS variables and data attributes
Sizes are controlled with data-size and colors with data-color. The component will inherit from the closest parent where these are set.
Suggestion uses elements from u-elements and therefore supports many of the same custom elements, data attributes, and CSS variables.
You can read more about how to use these in the u-elements u-combobox documentation.
| Name | Value |
|---|---|
| --dsc-suggestion-option-background--hover | var(--ds-color-surface-hover) |
| --dsc-suggestion-option-background--selected | var(--ds-color-surface-active) |
| --dsc-suggestion-option-border-radius | var(--ds-border-radius-md) |
| --dsc-suggestion-option-checkmark-size | var(--ds-size-6) |
| --dsc-suggestion-option-checkmark-url | url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='1em' height='1em' fill='none' viewBox='0 0 24 24'%3E%3Cpath fill='currentColor' fill-rule='evenodd' d='M18.998 6.94a.75.75 0 0 1 .063 1.058l-8 9a.75.75 0 0 1-1.091.032l-5-5a.75.75 0 1 1 1.06-1.06l4.438 4.437 7.471-8.405A.75.75 0 0 1 19 6.939' clip-rule='evenodd'/%3E%3C/svg%3E") |
| --dsc-suggestion-option-padding | var(--ds-size-3) |
| --dsc-suggestion-option-gap | var(--ds-size-2) |
| --dsc-suggestion-option-checkmark-border | max(2px,0.125rem) |
| --dsc-suggestion-option-checkmark-border-color | var(--ds-color-neutral-border-default) |
| --dsc-suggestion-clear-gap | var(--ds-size-2) |
| --dsc-suggestion-clear-padding | var(--ds-size-1) |
| --dsc-suggestion-clear-size | var(--ds-size-9) |
| --dsc-suggestion-clear-icon-url | url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24'%3E%3Cpath fill='currentColor' d='M6.53 5.47a.75.75 0 0 0-1.06 1.06L10.94 12l-5.47 5.47a.75.75 0 1 0 1.06 1.06L12 13.06l5.47 5.47a.75.75 0 1 0 1.06-1.06L13.06 12l5.47-5.47a.75.75 0 0 0-1.06-1.06L12 10.94z'/%3E%3C/svg%3E") |
| --dsc-suggestion-clear-border-radius | var(--ds-border-radius-md) |
| --dsc-suggestion-clear-background--hover | var(--ds-color-surface-hover) |
| --dsc-suggestion-clear-background--active | var(--ds-color-surface-active) |
| --dsc-suggestion-list-background | var(--ds-color-neutral-surface-default) |
| --dsc-suggestion-list-border-radius | var(--ds-border-radius-md) |
| --dsc-suggestion-list-box-shadow | var(--ds-shadow-md) |
| --dsc-suggestion-list-color | var(--ds-color-text-default) |
| --dsc-suggestion-list-offset | var(--ds-size-1) |
| --dsc-suggestion-list-gap | var(--ds-size-1) |
| --dsc-suggestion-list-placement | bottom |
| --dsc-suggestion-list-padding | var(--ds-size-3) var(--ds-size-2) |
| --dsc-suggestion-border-width | var(--ds-border-width-default) |
| --dsc-suggestion-border-style | solid |
| --dsc-suggestion-border-color | var(--ds-color-neutral-border-subtle) |
| --dsc-suggestion-chip-gap | var(--ds-size-1) |
| --dsc-suggestion-chip-background | var(--ds-color-base-default) |
| --dsc-suggestion-chip-background--hover | var(--ds-color-base-hover) |
| --dsc-suggestion-chip-border-width | var(--ds-border-width-default) |
| --dsc-suggestion-chip-height | var(--ds-size-8) |
| --dsc-suggestion-chip-font-size | var(--ds-body-sm-font-size) |
| --dsc-suggestion-chip-border-style | solid |
| --dsc-suggestion-chip-border-color | transparent |
| --dsc-suggestion-chip-border-radius | var(--ds-border-radius-full) |
| --dsc-suggestion-chip-padding | 0 var(--ds-size-3) |
| --dsc-suggestion-chip-color | var(--ds-color-base-contrast-default) |
| --dsc-suggestion-chip-icon-url | url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='1em' height='1em' fill='none' viewBox='0 0 24 24'%3E%3Cpath fill='currentColor' d='M6.53 5.47a.75.75 0 0 0-1.06 1.06L10.94 12l-5.47 5.47a.75.75 0 1 0 1.06 1.06L12 13.06l5.47 5.47a.75.75 0 1 0 1.06-1.06L13.06 12l5.47-5.47a.75.75 0 0 0-1.06-1.06L12 10.94z'/%3E%3C/svg%3E") |
| --dsc-suggestion-chip-icon-size | var(--ds-size-7) |
| --dsc-suggestion-chip-input-size | var(--ds-size-5) |
| --dsc-suggestion-chip-spacing | calc((var(--dsc-suggestion-chip-height) - var(--dsc-suggestion-chip-input-size))/2) |
| --dsc-button-size | var(--dsc-suggestion-clear-size) |
| Name | Value |
|---|---|
| data-multiple | false |
| data-is-floating | |
| data-overscroll | contain |
| data-floating | top, right, bottom, left |
| data-empty | |
| data-activedescendant |
Examples
Check the u-combobox documentation for more examples of how you can use suggestion.
Multiple choice
To allow users to select multiple options, use data-multiple on suggestion.
HTML
Unable to parse html
const MultipleEn = () => { const DATA_PLACES = [ 'Sogndal', 'Oslo', 'Brønnøysund', 'Stavanger', 'Trondheim', 'Bergen', 'Lillestrøm', ]; return ( <Field> <Label>Select a destination</Label> <EXPERIMENTAL_Suggestion multiple> <EXPERIMENTAL_Suggestion.Input /> <EXPERIMENTAL_Suggestion.Clear /> <EXPERIMENTAL_Suggestion.List> <EXPERIMENTAL_Suggestion.Empty> No results found </EXPERIMENTAL_Suggestion.Empty> {DATA_PLACES.map((place) => ( <EXPERIMENTAL_Suggestion.Option key={place}> {place} </EXPERIMENTAL_Suggestion.Option> ))} </EXPERIMENTAL_Suggestion.List> </EXPERIMENTAL_Suggestion> </Field> ); }; render(<MultipleEn />)
Adding new options
With data-creatable on suggestion, a user can create new selected options by entering text in the input and pressing Enter.
HTML
Unable to parse html
const CreatableEn = () => { const DATA_PLACES = [ 'Sogndal', 'Oslo', 'Brønnøysund', 'Stavanger', 'Trondheim', 'Bergen', 'Lillestrøm', ]; return ( <Field> <Label>Select destination</Label> <EXPERIMENTAL_Suggestion creatable multiple> <EXPERIMENTAL_Suggestion.Input /> <EXPERIMENTAL_Suggestion.Clear /> <EXPERIMENTAL_Suggestion.List> <EXPERIMENTAL_Suggestion.Empty> No results found, press enter to add </EXPERIMENTAL_Suggestion.Empty> {DATA_PLACES.map((place) => ( <EXPERIMENTAL_Suggestion.Option key={place}> {place} </EXPERIMENTAL_Suggestion.Option> ))} </EXPERIMENTAL_Suggestion.List> </EXPERIMENTAL_Suggestion> </Field> ); }; render(<CreatableEn />)
Clear selection
Add a button with type="reset" and aria-label to allow users to clear the selection.
HTML
Unable to parse html
const PreviewEn = () => { const DATA_PLACES = ['Sogndal', 'Oslo', 'Brønnøysund']; return ( <Field> <Label>Select a destination</Label> <EXPERIMENTAL_Suggestion> <EXPERIMENTAL_Suggestion.Input /> <EXPERIMENTAL_Suggestion.Clear /> <EXPERIMENTAL_Suggestion.List> <EXPERIMENTAL_Suggestion.Empty> No results found </EXPERIMENTAL_Suggestion.Empty> {DATA_PLACES.map((place) => ( <EXPERIMENTAL_Suggestion.Option key={place} label={place} value={place} > {place} <div>Municipality</div> </EXPERIMENTAL_Suggestion.Option> ))} </EXPERIMENTAL_Suggestion.List> </EXPERIMENTAL_Suggestion> </Field> ); }; render(<PreviewEn />)
React
- filter
- Description
Filter options; boolean or a custom callback. See {@link Filter} for the callback signature.
- Type
boolean | Filter- Default
true
- creatable
- Description
Allows the user to create new items
- Type
boolean- Default
false
- onBeforeMatch
- Description
Callback when matching input value against options
- Type
((event: EventBeforeMatch) => void)
- name
- Description
The name of the associated form control
- Type
string- Default
undefined
- renderSelected
- Description
Change how the selected options are rendered inside the `Chip`.
- Type
((args: { label: string; value: string; }) => ReactNode)- Default
({ label }) => label
- multiple
- Description
Allows the user to select multiple items
- Type
boolean- Default
false
- selected
- Description
The selected item of the Suggestion. If `label` and `value` are the same, each item can be a `string`. Otherwise, each item must be a `SuggestionItem`. Using this makes the component controlled and it must be used in combination with `onSelectedChange`.
- Type
string | SuggestionItem | (string | SuggestionItem)[] | null
- defaultSelected
- Description
Default selected item when uncontrolled
- Type
string | SuggestionItem | (string | SuggestionItem)[]
- onSelectedChange
- Description
Callback when selected items changes
- Type
((value: SuggestionItem | null) => void) | ((value: SuggestionItem[]) => void)
| Name | Type | Default | Description |
|---|---|---|---|
| filter | boolean | Filter | true | Filter options; boolean or a custom callback. See {@link Filter} for the callback signature. |
| creatable | boolean | false | Allows the user to create new items |
| onBeforeMatch | ((event: EventBeforeMatch) => void) | - | Callback when matching input value against options |
| name | string | undefined | The name of the associated form control |
| renderSelected | ((args: { label: string; value: string; }) => ReactNode) | ({ label }) => label | Change how the selected options are rendered inside the `Chip`. |
| multiple | boolean | false | Allows the user to select multiple items |
| selected | string | SuggestionItem | (string | SuggestionItem)[] | null | - | The selected item of the Suggestion. If `label` and `value` are the same, each item can be a `string`. Otherwise, each item must be a `SuggestionItem`. Using this makes the component controlled and it must be used in combination with `onSelectedChange`. |
| defaultSelected | string | SuggestionItem | (string | SuggestionItem)[] | - | Default selected item when uncontrolled |
| onSelectedChange | ((value: SuggestionItem | null) => void) | ((value: SuggestionItem[]) => void) | - | Callback when selected items changes |
SuggestionClear
- command
- Type
string
- commandfor
- Type
string
- aria-label
- Description
Aria label for the clear button
- Type
string- Default
Tøm
| Name | Type | Default | Description |
|---|---|---|---|
| command | string | - | - |
| commandfor | string | - | - |
| aria-label | string | Tøm | Aria label for the clear button |
SuggestionInput
- type
- Description
Supported `input` types
- Type
"number" | "hidden" | "color" | "checkbox" | "date" | "datetime-local" | "email" | "file" | "month" | "password" | "radio" | "search" | "tel" | "text" | "time" | "url" | "week"- Default
'text'
- disabled
- Description
Disables element @note Avoid using if possible for accessibility purposes
- Type
boolean
- readOnly
- Description
Toggle `readOnly`
- Type
boolean
- size
- Description
Defines the width of `Input` in count of characters.
- Type
number
| Name | Type | Default | Description |
|---|---|---|---|
| type | "number" | "hidden" | "color" | "checkbox" | "date" | "datetime-local" | "email" | "file" | "month" | "password" | "radio" | "search" | "tel" | "text" | "time" | "url" | "week" | 'text' | Supported `input` types |
| disabled | boolean | - | Disables element @note Avoid using if possible for accessibility purposes |
| readOnly | boolean | - | Toggle `readOnly` |
| size | number | - | Defines the width of `Input` in count of characters. |
SuggestionList
- singular
- Description
The screen reader announcement for singular Suggestion, where %d is the number of Suggestions
- Type
string- Default
%d forslag
- plural
- Description
The screen reader announcement for plural Suggestions, where %d is the number of Suggestions
- Type
string- Default
%d forslag
- autoPlacement
- Description
Whether to enable auto placement.
- Type
boolean- Default
true
| Name | Type | Default | Description |
|---|---|---|---|
| singular | string | %d forslag | The screen reader announcement for singular Suggestion, where %d is the number of Suggestions |
| plural | string | %d forslag | The screen reader announcement for plural Suggestions, where %d is the number of Suggestions |
| autoPlacement | boolean | true | Whether to enable auto placement. |
Controlled multiple choice example
If you need to control which options are selected, you can use selected and onSelectedChange on Suggestion.
React
Unable to parse html
const ControlledMultipleEn = () => { const DATA_PLACES = [ 'Sogndal', 'Oslo', 'Brønnøysund', 'Stavanger', 'Trondheim', 'Bergen', 'Lillestrøm', ]; const [selected, setSelected] = useState<string[]>(['Oslo']); return ( <> <Field> <Label>Select destinations</Label> <EXPERIMENTAL_Suggestion multiple selected={selected} onSelectedChange={(items) => setSelected(items.map((item) => item.value)) } > <EXPERIMENTAL_Suggestion.Input /> <EXPERIMENTAL_Suggestion.Clear /> <EXPERIMENTAL_Suggestion.List> <EXPERIMENTAL_Suggestion.Empty> No results found </EXPERIMENTAL_Suggestion.Empty> {DATA_PLACES.map((place) => ( <EXPERIMENTAL_Suggestion.Option key={place} label={place} value={place} > {place} <div>Municipality</div> </EXPERIMENTAL_Suggestion.Option> ))} </EXPERIMENTAL_Suggestion.List> </EXPERIMENTAL_Suggestion> </Field> <Divider style={{ marginTop: 'var(--ds-size-4)' }} /> <Paragraph style={{ margin: 'var(--ds-size-2) 0' }}> Selected destinations: {selected.join(', ')} </Paragraph> <Button onClick={() => { setSelected(['Sogndal', 'Stavanger']); }} > Set destinations to Sogndal, Stavanger </Button> </> ); }; render(<ControlledMultipleEn />)
Filter
Filter is enabled by default, and the filter searches based on the text in the input. You can pass your own filter. The example below shows how to turn the filter off entirely.
React
Unable to parse html
const Filter = () => { const DATA_PLACES = [ 'Sogndal', 'Oslo', 'Brønnøysund', 'Stavanger', 'Trondheim', 'Bergen', 'Lillestrøm', ]; return ( <Field> <Label>Skriv inn et tall mellom 1-6</Label> <EXPERIMENTAL_Suggestion filter={false}> <EXPERIMENTAL_Suggestion.Input /> <EXPERIMENTAL_Suggestion.Clear /> <EXPERIMENTAL_Suggestion.List> <EXPERIMENTAL_Suggestion.Empty> Ingen treff </EXPERIMENTAL_Suggestion.Empty> {DATA_PLACES.map((label) => ( <EXPERIMENTAL_Suggestion.Option key={label} value={label.toLowerCase()} > {label} </EXPERIMENTAL_Suggestion.Option> ))} </EXPERIMENTAL_Suggestion.List> </EXPERIMENTAL_Suggestion> </Field> ); }; render(<Filter />)