# List
Styled list elements for ordered, unordered, and definition lists
Section: components
Docs: https://nebula.kaluza.com/components/list
---
## When to use
- When presenting multiple items that benefit from visual separation rather than running together as prose
- When items have an inherent order, priority, or sequence that matters: use an ordered list (`Ol`)
- When items are discrete and parallel but have no meaningful order: use an unordered list (`Ul`)
- When presenting term-and-definition pairs such as a glossary or a set of key facts: use a definition list (`Dl` with `Dt` and `Dd`)
## When not to use
- **For two or fewer items** - these read more naturally as prose. Use [Text](/components/text) (`P`) instead.
- **For key-value data about an entity** - use [Summary List](/components/summary-list) instead, which is built for structured data rows and provides better semantics for that pattern.
- **For navigation links** - use the appropriate navigation component rather than a list of [Text Links](/components/text-link).
## Anatomy
**Unordered and ordered lists:**
1. **List container** (`Ul` or `Ol`)
2. **List item** (`Li`)
**Definition lists:**
1. **Definition list container** (`Dl`)
2. **Term** (`Dt`)
3. **Definition** (`Dd`)
## Basic usage
```tsx
- An example of body text.
-
An example with emphasis and strong text.
```
## Variants and features
### Unordered list
Use `Ul` when the items are discrete but their order carries no meaning. Items are presented with a disc bullet marker.
```tsx
- An example of body text.
-
An example with emphasis and strong text.
```
### Ordered list
Use `Ol` when the sequence matters, for example a set of steps or a ranked priority list. Items are numbered automatically.
```tsx
- Take a meter reading
- Enter it in your account
- Wait for your updated bill
```
### Definition list
Use `Dl` with `Dt` (term) and `Dd` (definition) for glossaries and term-definition pairs. Each term renders in semi-bold, and each definition follows immediately below it.
For structured data about an entity (such as an account summary), use [Summary List](/components/summary-list) instead.
```tsx
- MPAN
- A unique 13-digit number that identifies your electricity supply point.
- MPRN
- A unique number that identifies your gas supply point.
```
### Inline formatting
Use `Strong` or `Em` from the [Text](/components/text) component inside `Li`, `Dt`, or `Dd` elements to add emphasis where needed.
```tsx
- Direct debit (taken on the 1st of each month)
- Pay on receipt (pay when you receive your bill)
```
### Nested lists
Lists can be nested by placing a `Ul` or `Ol` inside a `Li`. Use nesting sparingly. More than two levels is usually a sign the content needs restructuring.
```tsx
- Electricity
- Standing charge
- Unit rate
- Gas
- Standing charge
- Unit rate
```
## Rules and guidance
### Usage
- **List content** MUST use Nebula List components (Ul, Ol, Li, Dl, Dt, Dd) rather than raw HTML list elements. Rationale: Nebula list components apply responsive typography and spacing from the design token system. [id: list.no-raw-elements; testability: auto]
- **Ul, Ol** SHOULD contain at least two items. Rationale: A single-item list provides no benefit over a paragraph. Use P instead. [id: list.minimum-items; testability: auto]
- **Ol** SHOULD be used when the order of items is meaningful (steps, priority, ranking). Rationale: Using Ol signals to users and assistive technology that sequence matters. Using Ul when order is meaningful removes this signal. [id: list.ordered-when-sequence-matters; testability: manual]
- **Ul** SHOULD be used when items are parallel but their order carries no meaning. [id: list.unordered-when-no-sequence; testability: manual]
- **Dl** SHOULD be used for term-and-definition pairs such as glossaries or factual key-value content. [id: list.dl-for-term-definition; testability: manual]
- **Dl** SHOULD NOT be used for structured data rows about an entity. Rationale: SummaryList is purpose-built for that pattern and provides better styling and semantics. [id: list.use-summary-list-for-entity-data; testability: manual]
- **Nested lists** SHOULD NOT exceed two levels of nesting. Rationale: Deep nesting is visually complex and usually signals that content needs restructuring. [id: list.nesting-depth; testability: auto]
### Content
- **Li, Dd** SHOULD be grammatically parallel across items in the same list. Rationale: Parallel structure makes lists easier to scan and understand. [id: list.item-parallelism; testability: manual]
- **Li** SHOULD NOT end with a full stop unless list items are complete sentences. [id: list.no-terminal-punctuation; testability: manual]
### Composition
- **Li** MUST be a direct child of Ul or Ol. Rationale: Placing Li outside a list container is invalid HTML and breaks screen reader list announcement. [id: list.li-inside-ul-ol; testability: auto]
- **Dt, Dd** MUST be direct children of Dl. [id: list.dt-dd-inside-dl; testability: auto]
- **Dt** MUST appear before the Dd element it describes. [id: list.dt-before-dd; testability: auto]
## Accessibility
### In brief
List is built from native `ul`, `ol`, `li`, `dl`, `dt` and `dd` elements. A screen reader can announce it as a list and say how many items it has, and reads a description list as terms with their details.
### What Nebula provides
- `Ul`, `Ol`, `Li`, `Dl`, `Dt` and `Dd` render the native elements of the same name.
- `Ul` and `Ol` keep their markers (disc and decimal).
- A `Dt` can be followed by more than one `Dd`.
### Accessible design
#### Use an ordered list only when the order matters
A numbered list tells the user the sequence carries meaning. Use an unordered list when it does not.
WCAG: [1.3.1 Info and Relationships](https://www.w3.org/WAI/WCAG22/Understanding/info-and-relationships.html)
#### Use a description list for terms and their details
A glossary or a set of key and value pairs is read as a pair when it is a `Dl`. A run of bold labels and values is read as unrelated lines.
WCAG: [1.3.1 Info and Relationships](https://www.w3.org/WAI/WCAG22/Understanding/info-and-relationships.html)
### Accessible development
#### Always put `Li` directly inside `Ul` or `Ol`, and `Dt` and `Dd` directly inside `Dl`
The elements only make a list when they are nested this way. A `Li` outside a list, or a `Dd` outside a `Dl`, is not announced as part of one.
WCAG: [1.3.1 Info and Relationships](https://www.w3.org/WAI/WCAG22/Understanding/info-and-relationships.html)
#### Never type a bullet or a number into the text of a paragraph
A hand-made list has no list structure, so a screen reader cannot say how many items there are or let the user skip it. Use the list components.
WCAG: [1.3.1 Info and Relationships](https://www.w3.org/WAI/WCAG22/Understanding/info-and-relationships.html)
### Accessibility testing status
Reviewed on 9 October 2026 against v17.0.1.
| Test | Status | Tested with |
| --- | --- | --- |
| Default state | Tested | axe-core via the Storybook test runner (CI) |
| Advanced states | Partially tested | axe-core via the Storybook test runner (CI) |
| Keyboard navigation | Manually tested | keyboard (reported by the owner) |
| Screen reader | Manually tested | VoiceOver with Chrome (reported by the owner) |
What each test covers:
- Default state: The component as it first appears, before anything is clicked or changed.
- Advanced states: Every other state and variant: error, open, selected, each size and style, and hovering.
- Keyboard navigation: Whether every function can be reached and used with the keyboard alone, with a visible focus ring.
- Screen reader: Whether a screen reader announces what is on screen correctly: the name, the role and any state.
What these statuses mean:
- Tested: Every check in this row passed in an automated run that repeats on every pull request.
- Manually tested: Every check in this row was run once, by a person or with a tool they started, and all of them passed. It is not re-run on every change.
- Partially tested: Some checks in this row were run once and passed; the rest are still to do.
- Issue found: A check in this row failed. The ticket is shown beside the status.
- Not tested: No check in this row has been run yet.
- Not applicable: This row does not apply to the component, for the reason given beside it.
The table records which checks were run on this component on its own. It is not a statement of WCAG conformance, and it does not cover how the component is used.
## Properties
### Ul
Also accepts all props from `HTMLAttributes`.
- `as` (type `ElementType`, optional). Renders the component as a different element or component, e.g. `as="section"`.
- `forwardedAs` (type `ElementType`, optional). Like `as`, but passed on to the component being wrapped, for when a styled wrapper should keep its own element and change the one beneath it.
### Ol
Also accepts all props from `HTMLAttributes`, `OlHTMLAttributes`.
- `as` (type `ElementType`, optional). Renders the component as a different element or component, e.g. `as="section"`.
- `forwardedAs` (type `ElementType`, optional). Like `as`, but passed on to the component being wrapped, for when a styled wrapper should keep its own element and change the one beneath it.
### Li
Also accepts all props from `HTMLAttributes`, `LiHTMLAttributes`.
- `as` (type `ElementType`, optional). Renders the component as a different element or component, e.g. `as="section"`.
- `forwardedAs` (type `ElementType`, optional). Like `as`, but passed on to the component being wrapped, for when a styled wrapper should keep its own element and change the one beneath it.
### Dl
Also accepts all props from `HTMLAttributes`.
- `as` (type `ElementType`, optional). Renders the component as a different element or component, e.g. `as="section"`.
- `forwardedAs` (type `ElementType`, optional). Like `as`, but passed on to the component being wrapped, for when a styled wrapper should keep its own element and change the one beneath it.
### Dt
Also accepts all props from `HTMLAttributes`.
- `as` (type `ElementType`, optional). Renders the component as a different element or component, e.g. `as="section"`.
- `forwardedAs` (type `ElementType`, optional). Like `as`, but passed on to the component being wrapped, for when a styled wrapper should keep its own element and change the one beneath it.
### Dd
Also accepts all props from `HTMLAttributes`.
- `as` (type `ElementType`, optional). Renders the component as a different element or component, e.g. `as="section"`.
- `forwardedAs` (type `ElementType`, optional). Like `as`, but passed on to the component being wrapped, for when a styled wrapper should keep its own element and change the one beneath it.
## Styles
Reference only; you do not need this to use the component. This is the CSS its styled elements use, with theme tokens shown by name. `${...}` is an interpolation (a theme token, a mixin or another value from the code). A `&[prop]` or `@when ` block holds CSS that applies only under that condition; these are notation, not valid CSS.
### ul
```css
list-style-type: disc;
margin-top: ${space[1]};
margin-bottom: ${space[1]};
margin-left: ${space[6]};
${mediaQueries.smallAndUp} {
margin-top: ${space[2]};
margin-bottom: ${space[2]};
}
&:first-child {
margin-top: 0;
}
&:last-child {
margin-bottom: 0;
}
```
### ol
```css
list-style-type: decimal;
margin-top: ${space[2]};
margin-bottom: ${space[1]};
margin-left: ${space[6]};
${mediaQueries.smallAndUp} {
margin-top: ${space[2]};
margin-bottom: ${space[2]};
}
&:first-child {
margin-top: 0;
}
&:last-child {
margin-bottom: 0;
}
```
### li
```css
font-family: ${fonts.body};
line-height: ${lineHeights.body};
font-weight: ${fontWeights.normal};
color: ${colors.body};
margin: ${space[2]} auto;
font-size: ${responsiveFontSizes.body[0]};
${mediaQueries.smallAndUp} {
font-size: ${responsiveFontSizes.body[1]};
}
&:first-child {
margin-top: 0;
}
&:last-child {
margin-bottom: 0;
}
```
### dl
```css
margin-top: ${space[1]};
margin-bottom: ${space[1]};
${mediaQueries.smallAndUp} {
margin-top: ${space[2]};
margin-bottom: ${space[2]};
}
&:first-child {
margin-top: 0;
}
&:last-child {
margin-bottom: 0;
}
```
### dt
```css
font-family: ${fonts.body};
line-height: ${lineHeights.body};
font-weight: ${fontWeights.semiBold};
color: ${colors.heading};
margin-top: ${space[2]};
margin-bottom: ${space[1]};
font-size: ${responsiveFontSizes.body[0]};
${mediaQueries.smallAndUp} {
font-size: ${responsiveFontSizes.body[1]};
margin-top: ${space[3]};
}
&:first-child {
margin-top: 0;
}
&:last-child {
margin-bottom: 0;
}
```
### dd
```css
font-family: ${fonts.body};
line-height: ${lineHeights.body};
font-weight: ${fontWeights.normal};
color: ${colors.body};
font-size: ${responsiveFontSizes.body[0]};
${mediaQueries.smallAndUp} {
font-size: ${responsiveFontSizes.body[1]};
}
dd + & {
margin-top: ${space[1]};
}
```