@dnb/eufemia
Version:
DNB Eufemia Design System UI Library
932 lines (855 loc) • 26.2 kB
Markdown
---
title: 'DrawerList'
description: 'The DrawerList component is a fragment inside other components.'
version: 11.0.0
generatedAt: 2026-04-21T13:57:51.404Z
checksum: 090b7d977ba4be5e2c4c04d199a30a4048416c59f443a56985df2f80629d9c40
---
```tsx
import { DrawerList } from '@dnb/eufemia/fragments'
```
The DrawerList component is a fragment inside other components.
It is used e.g. in the [Dropdown](/uilib/components/dropdown).
- [Source code](https://github.com/dnbexperience/eufemia/tree/main/packages/dnb-eufemia/src/components/fragments/drawer-list)
- [Docs code](https://github.com/dnbexperience/eufemia/tree/main/packages/dnb-design-system-portal/src/docs/uilib/components/fragments/drawer-list)
```js
// as array
const data = [
// Every data item can, beside "content" - contain what ever
{
// (optional) can be what ever
selectedKey: 'key_0',
// Item content as a string or array
content: 'Item 1 Content',
},
// more items ...
{
selectedKey: 'key_1',
content: ['Item 2 Value', 'Item 2 Content'],
},
{
selectedKey: 'key_2',
content: ['Item 3 Content A', 'Item 3 Content B'],
},
{
selectedKey: 'key_3',
content: ['Item 4 Content A', <>Custom Component</>],
},
]
// as object
const data = {
a: 'A',
b: 'B',
}
```
```tsx
render(
<Wrapper>
<ComponentBox
scope={{
data,
}}
hidePreview
hideToolbar
>
<DrawerList
optionsRender={({ Items, Item, data }) => (
<>
<Items />
<Item>Addition</Item>
{data.length > 1 && <li>Addition</li>}
</>
)}
/>
</ComponentBox>
</Wrapper>
)
```
When a DrawerList is open, it will set an HTML attribute on the main HTML Element called `data-dnb-drawer-list-active`. The attribute value will be the ID of the current DrawerList.
This can be used to handle z-index issues from within CSS only:
```css
html[data-dnb-drawer-list-active='DrawerList-ID'] {
/* Your css */
}
```
```tsx
render(
<Wrapper>
<ComponentBox
scope={{
data,
}}
>
{() => {
const DrawerListWithState = (props) => {
const [open, setOpen] = React.useState(false)
return (
<>
<ToggleButton
id="state-toggle-button"
text="Toggle"
checked={open}
icon={`chevron_${open ? 'up' : 'down'}`}
iconPosition="left"
onChange={({ checked }) => setOpen(checked)}
/>
<DrawerList
wrapperElement="#state-toggle-button"
skipPortal
data={data}
open={open}
onClose={() => setOpen(false)}
{...props}
/>
</>
)
}
return <DrawerListWithState />
}}
</ComponentBox>
</Wrapper>
)
```
```tsx
render(
<Wrapper>
<ComponentBox
data-visual-test="drawer-list"
scope={{
data,
}}
hideCode
>
<span className="dnb-drawer-list__list">
<ul className="dnb-drawer-list__options">
<li className="dnb-drawer-list__option first-of-type">
<span className="dnb-drawer-list__option__inner">
Brukskonto - Kari Nordmann
</span>
</li>
<li className="dnb-drawer-list__option dnb-drawer-list__option--selected">
<span className="dnb-drawer-list__option__inner">
<span className="dnb-drawer-list__option__item item-nr-1">
<NumberFormat.BankAccountNumber>
12345678902
</NumberFormat.BankAccountNumber>
</span>
<span className="dnb-drawer-list__option__item">
Sparekonto - Ole Nordmann
</span>
</span>
</li>
<li className="dnb-drawer-list__option">
<span className="dnb-drawer-list__option__inner">
<span className="dnb-drawer-list__option__item item-nr-1">
<NumberFormat.BankAccountNumber>
11345678962
</NumberFormat.BankAccountNumber>
</span>
<span className="dnb-drawer-list__option__item item-nr-2">
<a
className="dnb-anchor dnb-anchor--has-icon"
href="/uilib/components/fragments/drawer-list/"
>
Long link that will wrap over several lines
</a>
</span>
<span className="dnb-drawer-list__option__item">
Feriekonto - Kari Nordmann med et kjempelangt etternavnsen
</span>
</span>
</li>
<li className="dnb-drawer-list__option last-of-type">
<span className="dnb-drawer-list__option__inner">
<span className="dnb-drawer-list__option__item item-nr-1">
<NumberFormat.BankAccountNumber>
15349648901
</NumberFormat.BankAccountNumber>
</span>
<span className="dnb-drawer-list__option__item">
Oppussing - Ole Nordmann
</span>
</span>
</li>
<li className="dnb-drawer-list__arrow" />
</ul>
</span>
</ComponentBox>
</Wrapper>
)
```
### Default DrawerList
```tsx
render(
<Wrapper>
<ComponentBox
scope={{
data,
}}
>
<DrawerList
skipPortal
open
preventClose
arrowPosition="left"
data={data}
value={3}
onChange={({ data: selectedDataItem }) => {
console.log('onChange', selectedDataItem)
}}
onOpen={() => {
console.log('onOpen')
}}
observerElement=".dnb-live-preview" // prevents direction to change when scrolling in this example
/>
</ComponentBox>
</Wrapper>
)
```
```tsx
render(
<Wrapper>
<ComponentBox data-visual-test="drawer-list-disabled">
<DrawerList
skipPortal
open
preventClose
data={[
{
content: 'Item 1',
},
{
content: 'Item 2, disabled',
disabled: true,
},
{
content: 'Item 3',
},
]}
observerElement=".dnb-live-preview" // prevents direction to change when scrolling in this example
/>
</ComponentBox>
</Wrapper>
)
```
### Custom event and link on single item
```tsx
render(
<Wrapper>
<ComponentBox
scope={{
data,
}}
>
{() => {
const CustomComponent = () => (
<CustomComponentInner
onTouchStart={preventDefault}
onClick={(e) => {
console.log('Do something different')
preventDefault(e)
}}
>
Custom event handler
</CustomComponentInner>
)
const CustomComponentInner = styled.span`
display: block;
width: 100%;
margin: -1rem -2rem -1rem -1rem;
padding: 1rem 2rem 1rem 1rem;
`
const preventDefault = (e) => {
e.stopPropagation()
e.preventDefault()
}
const CustomWidth = styled(DrawerList)`
.dnb-drawer-list__list {
width: var(--drawer-list-width);
}
`
return (
<CustomWidth
skipPortal
open
preventClose
right
title="Choose an item"
data={() => [
<Link key="link" href="/">
Go to this Link
</Link>,
'Or press on me',
<CustomComponent key="custom" />,
]}
onChange={({ value }) => {
console.log('More menu:', value)
}}
suffix={
<HelpButton title="Modal Title">Modal content</HelpButton>
}
observerElement=".dnb-live-preview" // prevents direction to change when scrolling in this example
/>
)
}}
</ComponentBox>
</Wrapper>
)
```
**NB:** By using this method you lose currently a lot of the core functionality like keyboard support and other accessibility features.
```tsx
render(
<Wrapper>
<ComponentBox>
{() => {
const list = [
{
value: 'A',
},
{
value: 'B',
},
{
value: 'C',
},
]
const CustomWidth = styled(DrawerList)`
.dnb-drawer-list__list {
width: var(--drawer-list-width);
}
`
const DrawerListWithState = () => {
const [selected, setSelected] = React.useState('C')
return (
<CustomWidth skipPortal open preventClose>
<DrawerList.Options>
{list.map(({ value, ...props }, i) => (
<DrawerList.Item
key={i}
selected={value === selected}
value={value}
onClick={({ value }) => setSelected(value)}
{...props}
>
{value}
</DrawerList.Item>
))}
</DrawerList.Options>
</CustomWidth>
)
}
return <DrawerListWithState />
}}
</ComponentBox>
</Wrapper>
)
```
```tsx
render(
<Wrapper>
<ComponentBox>
<DrawerList
skipPortal
open
preventClose
observerElement=".dnb-live-preview" // prevents direction to change when scrolling in this example
>
<DrawerList.Options>
<DrawerList.Item
style={{
color: 'red',
}}
key="A"
selected={false}
value="A"
onClick={() => {
console.log('onClick')
}}
>
Item 1
</DrawerList.Item>
<DrawerList.HorizontalItem
style={{
color: 'green',
}}
key="B"
selected={false}
value="B"
>
Item 2
</DrawerList.HorizontalItem>
</DrawerList.Options>
</DrawerList>
</ComponentBox>
</Wrapper>
)
```
```tsx
render(
<Wrapper>
<ComponentBox data-visual-test="drawer-list-inline-style">
<DrawerList
skipPortal
open
preventClose
data={[
{
content:
'They may be very large, like pneumonoultramicroscopicsilicovolcanoconiosis, a 45-letter hippopotomonstrosesquipedalian word for black lung disease.',
style: {
hyphens: 'auto',
color: 'red',
},
},
{
content:
'The longest word in the Oxford English Dictionary is the 45-letter pneumonoultramicroscopicsilicovolcanoconiosis, which refers to a form of lung disease.',
style: {
hyphens: 'none',
color: 'green',
},
},
{
content:
'According to the Oxford English Dictionary the longest word in the language is pneumonoultramicroscopicsilicovolcanoconiosis, with 45 letters.',
style: {
hyphens: 'manual',
color: 'blue',
},
},
]}
observerElement=".dnb-live-preview" // prevents direction to change when scrolling in this example
/>
</ComponentBox>
</Wrapper>
)
```
### Groups
If an item has a `groupIndex` property, it will use the groups in the `groups` property. Only the first group can be without title, all other groups must have a title.
```tsx
render(
<Wrapper>
<ComponentBox data-visual-test="drawer-list-groups">
<DrawerList
skipPortal
open
preventClose
observerElement=".dnb-live-preview" // prevents direction to change when scrolling in this example
groups={[undefined, 'Pets', undefined, 'Cars']}
data={[
{
groupIndex: 0,
content: 'Default 1',
},
{
groupIndex: 0,
content: 'Default 2',
},
{
groupIndex: 1,
content: 'Cat',
},
{
groupIndex: 1,
content: 'Dog',
},
{
groupIndex: 2,
content: 'Something',
},
{
groupIndex: 3,
content: 'Jeep',
},
{
groupIndex: 3,
content: 'Van',
},
{
content: 'No group',
},
]}
/>
</ComponentBox>
</Wrapper>
)
```
## Properties
```json
{
"props": {
"[data](#the-data-property)": {
"doc": "The data we want to fill the list with. [Details on the type of {DATA} can be found below](#the-data-property). The data can be provided as an array or object. Or as a function that returns the data (called when user opens the list).",
"type": ["{DATA}", "() => {DATA}"],
"status": "required"
},
"groups": {
"doc": "An array of group titles for the list items. Only the first group can be `undefined`.",
"type": "Array<React.ReactNode>",
"status": "optional"
},
"value": {
"doc": "Define a preselected `data` entry. In order of priority, `value` can be set to: object key (if `data` is an object), `selectedKey` property (if `data` is an array), array index (if no `selectedKey`) or content (if `value` is a non-integer string).",
"type": ["string", "number"],
"status": "optional"
},
"defaultValue": {
"doc": "Define a startup value or handle a re-render without handling the state during the re-render by yourself. Defaults to `null`.",
"type": "number",
"status": "optional"
},
"arrowPosition": {
"doc": "Position of the arrow on the popup drawer. Set to `left` or `right`. Defaults to `left` if not set.",
"type": "string",
"status": "optional"
},
"direction": {
"doc": "Defines the direction of how the drawer-list shows the options list. Can be `bottom` or `top`. Defaults to `auto`.",
"type": ["\"auto\"", "\"top\"", "\"bottom\""],
"status": "optional"
},
"labelDirection": {
"doc": "Use `labelDirection=\"horizontal\"` to change the label layout direction. Defaults to `vertical`.",
"type": "string",
"status": "optional"
},
"preventSelection": {
"doc": "If set to `true`, the DrawerList will then not make any permanent selection.",
"type": "boolean",
"status": "optional"
},
"focusable": {
"doc": "If set to `true`, the element is then focusable by assertive technologies.",
"type": "boolean",
"status": "optional"
},
"preventClose": {
"doc": "If set to `true`, the DrawerList will not close on any events.",
"type": "boolean",
"status": "optional"
},
"keepOpen": {
"doc": "If set to `true`, the DrawerList will close on outside clicks, but not on selection.",
"type": "boolean",
"status": "optional"
},
"independentWidth": {
"doc": "If set to `true`, the DrawerList will handle its width and position independently of the parent/mother element.",
"type": "boolean",
"status": "optional"
},
"fixedPosition": {
"doc": "If set to `true`, the DrawerList will be fixed in its scroll position by using CSS `position: fixed;`.",
"type": "boolean",
"status": "optional"
},
"enableBodyLock": {
"doc": "If set to `true`, the HTML body will get locked from scrolling when the Dropdown is open.",
"type": "boolean",
"status": "optional"
},
"skipKeysearch": {
"doc": "If set to `true`, search items by the first key will be ignored.",
"type": "boolean",
"status": "optional"
},
"ignoreEvents": {
"doc": "If set to `true`, all keyboard and mouse events will be ignored.",
"type": "boolean",
"status": "optional"
},
"alignDrawer": {
"doc": "Use 'right' to change the options alignment direction. Makes only sense to use in combination with `preventSelection` - or if an independent width is used.",
"type": "string",
"status": "optional"
},
"listClass": {
"doc": "Define an HTML class that will be set on the list, beside `dnb-drawer-list__list`.",
"type": "string",
"status": "optional"
},
"portalClass": {
"doc": "Define an HTML class that will be set on the DOM portal beside `dnb-drawer-list__portal__style`. Can be useful to handle e.g. a custom `z-index` in relation to a header.",
"type": "string",
"status": "optional"
},
"scrollable": {
"doc": "Defines if the options list should be scrollable (the `max-height` is set by default to `50vh`).",
"type": "boolean",
"status": "optional"
},
"noScrollAnimation": {
"doc": "To disable scrolling animation.",
"type": "boolean",
"status": "optional"
},
"noAnimation": {
"doc": "To disable appear/disappear (show/hide) animation.",
"type": "boolean",
"status": "optional"
},
"skipPortal": {
"doc": "To disable the React Portal behavior.",
"type": "boolean",
"status": "optional"
},
"minHeight": {
"doc": "Defines the minimum height (in `rem`) of the options list.",
"type": "string",
"status": "optional"
},
"maxHeight": {
"doc": "Defines the maximum height (in `rem`) of the options list.",
"type": "string",
"status": "optional"
},
"pageOffset": {
"doc": "Defines the available scrollable height. If scrolling should not change the height of the drawer-list, then set it to `0` (useful if the DrawerList is used in fixed positions on contrast to a scrollable page content).",
"type": "string",
"status": "optional"
},
"observerElement": {
"doc": "Set a HTML element, either as a selector or a DOM element. Can be used to send in an element which will be used to make the direction calculation on.",
"type": "string",
"status": "optional"
},
"cacheHash": {
"doc": "Set a `cacheHash` as a string to enable internal memorizing of the list to enhance rerendering performance. Components like Autocomplete are using this because of the huge data changes due to search and reorder.",
"type": "string",
"status": "optional"
},
"wrapperElement": {
"doc": "Has to be an HTML Element, or a selector for one, ideally a mother element, used to calculate sizes and distances. Also used for the 'click outside' detection. Clicking on the `wrapperElement` will not trigger an outside click.",
"type": ["string", "HTMLElement"],
"status": "optional"
},
"optionsRender": {
"doc": "Has to be a function, returning the items again. See [example](/uilib/components/fragments/drawer-list#example-usage-of-optionsRender). This can be used to add additional options above the actual rendered list.",
"type": "function",
"status": "optional"
},
"[Space](/uilib/layout/space/properties)": {
"doc": "Spacing properties like `top` or `bottom` are supported.",
"type": ["string", "object"],
"status": "optional"
}
}
}
```
The `data` can be structured in two main ways:
- As an array
- As an object.
An array is preferred as it gives you the most options.
```ts
// an array can contain complex items and offers the most control
const data = [
{
content: "Item 1",
},
{
content: <span>Item 2</span>
},
{
content: ["Item 3", "Line 2", <span>Line 3</span>]
},
{
content: ['Main account', '1234 12 12345'],
selectedValue: 'Main account (605,22 kr)',
suffixValue: '605,22 kr',
},
{
content: ['Old account', <i>Closed</i>],
disabled: true,
suffixValue: '0,00 kr',
},
]
// If you only use the `content` property, you can use it directly in the array.
// This list is identical to the one above:
const data = [
"Item 1",
<span>Item 2</span>,
["Item 3", "Line 2", <span>Line 3</span>],
{
content: ['Main account', '1234 12 12345'],
selectedValue: 'Main account (605,22 kr)',
suffixValue: '605,22 kr',
},
{
content: ['Old account', <i>Closed</i>],
disabled: true,
suffixValue: '0,00 kr',
},
]
const onChange = ({ data, value }) => {
console.log(data) // returns the item as it appears in the array
console.log(value) // returns the index of the item
}
```
Each object in the array have the following properties:
```json
{
"props": {
"content": {
"doc": "Visual content in the list item.",
"type": [
"string",
"React.ReactNode",
"Array<(string | React.ReactNode)>"
],
"status": "optional"
},
"disabled": {
"doc": "Disables the list item from selection.",
"type": "boolean",
"status": "optional"
},
"groupIndex": {
"doc": "What group index in the `groups` property this item belongs to.",
"type": "number",
"status": "optional"
},
"selectedKey": {
"doc": "If set, can be used instead of array index by the `value` prop.",
"type": ["string", "number"],
"status": "optional"
},
"selectedValue": {
"doc": "Replaces the standard value output for selected item. Only used in some implementations (Dropdown, Autocomplete).",
"type": ["string", "React.ReactNode"],
"status": "optional"
},
"suffixValue": {
"doc": "Content placed to the right in the list item.",
"type": ["string", "React.ReactNode"],
"status": "optional"
}
}
}
```
A simpler alternative, but with less options
```ts
// Each entry can contain the same type of value as the array's `content` property
const data = {
first: "Item 1",,
second: <span>Item 2</span>,
last: ["Item 3", "Line 2", <span>Line 3</span>],
}
const onChange = ({ data, value }) => {
console.log(data)
// returns a generated object representing the item:
// {
// selectedKey: 'first',
// value: 'first',
// content: 'Item 1',
// type: 'object'
// }
console.log(value) // returns the key ("first", "second", or "last"), instead of an index
}
```
The following is an overview of all the types that the `data` property accepts. (These are not actual names of actual types in the library.)
```ts
// The visual content that is shown in one DrawerList item.
// An array can be used to define multiple lines.
type CONTENT = string | React.ReactNode | (string | React.ReactNode)[]
// An array item
type ARRAY_OBJECT = {
content: CONTENT
disabled?: boolean
selectedKey?: string | number
selectedValue?: string | React.ReactNode
suffixValue?: string | React.ReactNode
style?: React.CSSProperties
}
// `data` as an array. A list of "ARRAY_OBJECT" types is preferred,
// but the "CONTENT" type can be useful for simple lists.
type ARRAY = (CONTENT | ARRAY_OBJECT)[]
// `data` as an object. Can only contain the "CONTENT" type.
// Each `key` behaves like the "ARRAY_OBJECT"'s `selectedKey`.
type RECORD = Record<string, CONTENT>
// An object or array that represents the entire DrawerList list.
type DATA = ARRAY | RECORD
// The final type of the `data` property:
let data: DATA | () => DATA
```
There is technically support for sending in a JSON string of the data to the `data` property. But this is an old functionality that we do not really support anymore.
```json
{
"locales": ["da-DK", "en-GB", "nb-NO", "sv-SE"],
"entries": {
"DrawerList.defaultGroupSR": {
"nb-NO": "Standardvalg",
"en-GB": "Default options",
"sv-SE": "Standardval",
"da-DK": "Standardvalg"
},
"DrawerList.missingGroup": {
"nb-NO": "Gruppe",
"en-GB": "Group",
"sv-SE": "Grupp",
"da-DK": "Gruppe"
},
"DrawerList.noGroupSR": {
"nb-NO": "Andre valg",
"en-GB": "Other options",
"sv-SE": "Andra val",
"da-DK": "Andre valg"
}
}
}
```
```json
{
"props": {
"onPreChange": {
"doc": "Will be called before `onChange`, this way you can return false to prevent selection and to prevent `onChange` execution.",
"type": "function",
"status": "optional"
},
"onChange": {
"doc": "Will be called on state changes made by the user.",
"type": "function",
"status": "optional"
},
"onSelect": {
"doc": "Will be called once the user focuses or selects an item by a click or keyboard navigation.",
"type": "function",
"status": "optional"
},
"onOpen": {
"doc": "Will be called once the user presses the drawer-list.",
"type": "function",
"status": "optional"
},
"onClose": {
"doc": "Will be called once the user presses the drawer-list again, or clicks somewhere else.",
"type": "function",
"status": "optional"
}
}
}
```
The difference between `onChange` and `onSelect` is:
- `onChange` will be called when the state changes, either with a **click** or **space/enter** keypress confirmation.
- `onSelect` differs most when the user is navigating by keyboard. Once the user is pressing e.g. the arrow keys, the selection is changing, but not the state.