Components
List
Lists are continuous, vertical indexes of text and images and video.- Use lists to help users find a specific item and act on it
- Order list items in logical ways (like alphabetical or numerical)
- Three sizes: one-line, two-line, and three-line
- Keep items short and easy to scan
- Show icons, text, and actions in a consistent format
-
Headline
-
Headline
Supporting text that truly is quite long enough to fill up multiple lines.
-
Trailing supporting text
100+ -
Trailing keyboard command
CTRL+Shift+X -
Headline with start icon
-
Headline with start icon
Supporting text that truly is quite long enough to fill up multiple lines.
-
Inset class
Makes the text line up nicely
-
Link list item
-
OP
Headline
-
Headline
Supporting text
-
Link with start icon
-
End icon button
---import { List } from "@opui/astro"import ListAll from "../ListAll.astro"---
<List> <ListAll prefix="default-" /></List><ul class="list"> <li> <div class="text"><p>Headline</p></div> </li> <li> <div class="text"> <p>Headline</p> <p> Supporting text that truly is quite long enough to fill up multiple lines. </p> </div> </li> <li> <div class="text"><p>Trailing supporting text</p></div> <div class="end"><div>100+</div></div> </li> <li> <div class="text"><p>Trailing keyboard command</p></div> <div class="end"> <div><kbd>CTRL+Shift+X</kbd></div> </div> </li> <li class="border-top"> <div class="start"> <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32" > <path fill="currentColor" d="M16 16a7 7 0 1 0 0-14a7 7 0 0 0 0 14m-8.5 2A3.5 3.5 0 0 0 4 21.5v.5c0 2.393 1.523 4.417 3.685 5.793C9.859 29.177 12.802 30 16 30s6.14-.823 8.315-2.207C26.477 26.417 28 24.393 28 22v-.5a3.5 3.5 0 0 0-3.5-3.5z" ></path> </svg> </div> <div class="text"><p>Headline with start icon</p></div> </li> <li> <div class="start"> <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32" > <path fill="currentColor" d="M16 16a7 7 0 1 0 0-14a7 7 0 0 0 0 14m-8.5 2A3.5 3.5 0 0 0 4 21.5v.5c0 2.393 1.523 4.417 3.685 5.793C9.859 29.177 12.802 30 16 30s6.14-.823 8.315-2.207C26.477 26.417 28 24.393 28 22v-.5a3.5 3.5 0 0 0-3.5-3.5z" ></path> </svg> </div> <div class="text"> <p>Headline with start icon</p> <p> Supporting text that truly is quite long enough to fill up multiple lines. </p> </div> </li> <li class="inset"> <div class="text"> <p>Inset class</p> <p>Makes the text line up nicely</p> </div> </li> <li class="border-top"> <button> <div class="text"><p>Button list item</p></div> </button> </li> <li> <a href="#"> <div class="text"><p>Link list item</p></div> </a> </li> <li class="border-top"> <div class="start"><div slot="start" class="avatar">OP</div></div> <div class="text"><p>Headline</p></div> </li> <li> <div class="start"> <div slot="start" class="avatar"> <img src="https://images.unsplash.com/photo-1614530606961-c4ce986825c1?q=80&w=1827&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" alt="" /> </div> </div> <div class="text"> <p>Headline</p> <p>Supporting text</p> </div> </li> <li class="border-top"> <button> <div class="start"> <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 28 28" > <path fill="currentColor" d="M21.75 3A3.25 3.25 0 0 1 25 6.25v15.5A3.25 3.25 0 0 1 21.75 25H6.25A3.25 3.25 0 0 1 3 21.75V6.25A3.25 3.25 0 0 1 6.25 3zm0 1.5H6.25A1.75 1.75 0 0 0 4.5 6.25V15h6a.75.75 0 0 1 .743.648l.007.102a2.75 2.75 0 1 0 5.5 0a.75.75 0 0 1 .648-.743L17.5 15h6V6.25a1.75 1.75 0 0 0-1.75-1.75" ></path> </svg> </div> <div class="text"><p>Button with start icon</p></div> </button> </li> <li> <a href="#"> <div class="start"> <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24" > <path fill="currentColor" d="m13.94 5l5.061 5.06L9.063 20a2.25 2.25 0 0 1-1 .58l-5.115 1.395a.75.75 0 0 1-.92-.921l1.394-5.116a2.25 2.25 0 0 1 .58-.999zm-7.414 6l-1.5 1.5H2.75a.75.75 0 0 1 0-1.5zm14.352-8.174l.153.144l.145.153a3.58 3.58 0 0 1-.145 4.908l-.97.969L15 3.94l.97-.97a3.58 3.58 0 0 1 4.908-.144M10.526 7l-1.5 1.5H2.75a.75.75 0 1 1 0-1.5zm4-4l-1.5 1.5H2.75a.75.75 0 1 1 0-1.5z" ></path> </svg> </div> <div class="text"><p>Link with start icon</p></div> </a> </li> <li class="border-top"> <button> <div class="text"><p>End icon</p></div> <div class="end"> <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32" > <path fill="currentColor" d="M11.116 26.634a1.25 1.25 0 0 1 0-1.768L19.982 16l-8.866-8.866a1.25 1.25 0 0 1 1.768-1.768l9.75 9.75a1.25 1.25 0 0 1 0 1.768l-9.75 9.75a1.25 1.25 0 0 1-1.768 0" ></path> </svg> </div> </button> </li> <li> <div class="text"><p>End icon button</p></div> <div class="end"> <div> <button class="icon-button"> <span class="sr-only">More</span> <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32" > <path fill="currentColor" d="M16 9.5a2.5 2.5 0 1 1 0-5a2.5 2.5 0 0 1 0 5m0 9a2.5 2.5 0 1 1 0-5a2.5 2.5 0 0 1 0 5M13.5 25a2.5 2.5 0 1 0 5 0a2.5 2.5 0 0 0-5 0" ></path> </svg> </button> </div> </div> </li> <li class="border-top"> <label class="checkbox" for="default-checkbox-all"> <div class="text"><div>Checkbox</div></div> <div class="end"> <input type="checkbox" id="default-checkbox-all" slot="end" /> </div> </label> </li> <li class="border-top"> <label class="radio" for="default-radio-all-1"> <div class="text"><div>Radio 1</div></div> <div class="end"> <input type="radio" id="default-radio-all-1" name="default-radio-group-all" slot="end" /> </div> </label> </li> <li> <label class="radio" for="default-radio-all-2"> <div class="text"><div>Radio 2</div></div> <div class="end"> <input type="radio" id="default-radio-all-2" name="default-radio-group-all" slot="end" /> </div> </label> </li> <li class="border-top"> <label class="switch" for="default-switch-all-1"> <div class="text"><div>Switch 1</div></div> <div class="end"> <input type="checkbox" role="switch" id="default-switch-all-1" slot="end" /> </div> </label> </li></ul>Configurations
A List item is split up in three parts:
-
textslot: main content -
startslot (optional): items before the main content -
endslot (optional): items after the main content
With great power...
The List component is extremely flexible and versatile. Be careful if you start creating new configurations on your own. Maybe an existing one can solve your problem, but in another way?
Variants
Change background color with the variant prop.
-
Filled (default)
-
Second item
-
Default
-
Second item
-
Tonal
-
Second item
-
Transparent
-
Second item
---import { List, ListItem } from "@opui/astro"---
<div class="column" style="gap: var(--size-4);"> <List> <ListItem headline="Filled (default)" /> <ListItem headline="Second item" /> </List>
<List variant="default"> <ListItem headline="Default" /> <ListItem headline="Second item" /> </List>
<List variant="tonal"> <ListItem headline="Tonal" /> <ListItem headline="Second item" /> </List>
<List variant="transparent"> <ListItem headline="Transparent" /> <ListItem headline="Second item" /> </List></div><div class="column" style="gap: var(--size-4)"> <ul class="list"> <li> <div class="text"><p>Filled (default)</p></div> </li> <li> <div class="text"><p>Second item</p></div> </li> </ul> <ul class="list default"> <li> <div class="text"><p>Default</p></div> </li> <li> <div class="text"><p>Second item</p></div> </li> </ul> <ul class="list tonal"> <li> <div class="text"><p>Tonal</p></div> </li> <li> <div class="text"><p>Second item</p></div> </li> </ul> <ul class="list transparent"> <li> <div class="text"><p>Transparent</p></div> </li> <li> <div class="text"><p>Second item</p></div> </li> </ul></div>Clickable list item
Wrap the elements of your List item with a a, button
or label depending on use-case.
---import { List } from "@opui/astro"import { ListItem } from "@opui/astro"import { CheckboxInput } from "@opui/astro"---
<List> <ListItem as="button" type="button" headline="Button list item" /> <ListItem as="a" href="#clickable-list-item" headline="Link list item" /> <ListItem type="checkbox" for="clickable-checkbox"> <div slot="text">Checkbox list item</div> <CheckboxInput slot="end" id="clickable-checkbox" name="checkbox" /> </ListItem></List><ul class="list"> <li> <button> <div class="text"><p>Button list item</p></div> </button> </li> <li> <a href="#clickable-list-item" ><div class="text"><p>Link list item</p></div></a > </li> <li> <label class="checkbox" for="clickable-checkbox"> <div class="text"><div>Checkbox list item</div></div> <div class="end"> <input type="checkbox" id="clickable-checkbox" name="checkbox" slot="end" /> </div> </label> </li></ul>Selected item
Add aria-selected="true" to the ListItem.
---import { List, ListItem } from "@opui/astro"---
<List> <ListItem aria-selected="true"> <a href="#"> <div class="text"> <p>Selected item</p> <p>This item has aria-selected="true" applied to the ListItem</p> </div> </a> </ListItem> <ListItem> <a href="#"> <div class="text"> <p>Normal item</p> </div> </a> </ListItem></List><ul class="list"> <li aria-selected="true"> <a href="#"> <div class="text"> <p>Selected item</p> <p>This item has aria-selected="true" applied to the ListItem</p> </div> </a> </li> <li> <a href="#"> <div class="text"><p>Normal item</p></div> </a> </li></ul>Text
Main text lives in the text slot, or pass headline
and description props directly on ListItem.
-
Headline
-
Headline
Supporting text that truly is quite long enough to fill up multiple lines.
-
Headline
Supporting text
Even more supporting text
---import { List } from "@opui/astro"import { ListItem } from "@opui/astro"---
<List> <ListItem headline="Headline" /> <ListItem headline="Headline" description="Supporting text that truly is quite long enough to fill up multiple lines." /> <ListItem headline="Headline"> <p>Supporting text</p> <p>Even more supporting text</p> </ListItem></List><ul class="list"> <li> <div class="text"><p>Headline</p></div> </li> <li> <div class="text"> <p>Headline</p> <p> Supporting text that truly is quite long enough to fill up multiple lines. </p> </div> </li> <li> <div class="text"> <p>Headline</p> <p>Supporting text</p> <p>Even more supporting text</p> </div> </li></ul>Start items
Authored via the start slot on ListItem.
Icon
-
Headline
-
Headline
Supporting text
---import { List } from "@opui/astro"import { ListItem } from "@opui/astro"---
<List> <ListItem headline="Headline"> <svg slot="start" xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32" > <path fill="currentColor" d="M16 16a7 7 0 1 0 0-14a7 7 0 0 0 0 14m-8.5 2A3.5 3.5 0 0 0 4 21.5v.5c0 2.393 1.523 4.417 3.685 5.793C9.859 29.177 12.802 30 16 30s6.14-.823 8.315-2.207C26.477 26.417 28 24.393 28 22v-.5a3.5 3.5 0 0 0-3.5-3.5z" ></path> </svg> </ListItem> <ListItem headline="Headline" description="Supporting text"> <svg slot="start" xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32" > <path fill="currentColor" d="M16 16a7 7 0 1 0 0-14a7 7 0 0 0 0 14m-8.5 2A3.5 3.5 0 0 0 4 21.5v.5c0 2.393 1.523 4.417 3.685 5.793C9.859 29.177 12.802 30 16 30s6.14-.823 8.315-2.207C26.477 26.417 28 24.393 28 22v-.5a3.5 3.5 0 0 0-3.5-3.5z" ></path> </svg> </ListItem></List><ul class="list"> <li> <div class="start"> <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32" > <path fill="currentColor" d="M16 16a7 7 0 1 0 0-14a7 7 0 0 0 0 14m-8.5 2A3.5 3.5 0 0 0 4 21.5v.5c0 2.393 1.523 4.417 3.685 5.793C9.859 29.177 12.802 30 16 30s6.14-.823 8.315-2.207C26.477 26.417 28 24.393 28 22v-.5a3.5 3.5 0 0 0-3.5-3.5z" ></path> </svg> </div> <div class="text"><p>Headline</p></div> </li> <li> <div class="start"> <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32" > <path fill="currentColor" d="M16 16a7 7 0 1 0 0-14a7 7 0 0 0 0 14m-8.5 2A3.5 3.5 0 0 0 4 21.5v.5c0 2.393 1.523 4.417 3.685 5.793C9.859 29.177 12.802 30 16 30s6.14-.823 8.315-2.207C26.477 26.417 28 24.393 28 22v-.5a3.5 3.5 0 0 0-3.5-3.5z" ></path> </svg> </div> <div class="text"> <p>Headline</p> <p>Supporting text</p> </div> </li></ul>Avatar
Read more: Avatar
-
AB
Headline
-
Headline
Supporting text
---import { List } from "@opui/astro"import { ListItem } from "@opui/astro"import { Avatar } from "@opui/astro"---
<List> <ListItem headline="Headline"> <Avatar slot="start">AB</Avatar> </ListItem> <ListItem headline="Headline" description="Supporting text"> <Avatar slot="start"> <img src="https://images.unsplash.com/photo-1614530606961-c4ce986825c1?q=80&w=1827&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" alt="" /> </Avatar> </ListItem></List><ul class="list"> <li> <div class="start"><div slot="start" class="avatar">AB</div></div> <div class="text"><p>Headline</p></div> </li> <li> <div class="start"> <div slot="start" class="avatar"> <img src="https://images.unsplash.com/photo-1614530606961-c4ce986825c1?q=80&w=1827&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" alt="" /> </div> </div> <div class="text"> <p>Headline</p> <p>Supporting text</p> </div> </li></ul>Image
-
Headline
Supporting text
-
Headline
Supporting text
---import { List } from "@opui/astro"import { ListItem } from "@opui/astro"---
<List> <ListItem headline="Headline" description="Supporting text"> <img slot="start" src="https://images.unsplash.com/photo-1504579264001-833438f93df2?q=80&w=1738&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" alt="" /> </ListItem> <ListItem headline="Headline" description="Supporting text"> <img slot="start" src="https://images.unsplash.com/photo-1504579264001-833438f93df2?q=80&w=1738&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" alt="" /> </ListItem></List><ul class="list"> <li> <div class="start"> <img src="https://images.unsplash.com/photo-1504579264001-833438f93df2?q=80&w=1738&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" alt="" /> </div> <div class="text"> <p>Headline</p> <p>Supporting text</p> </div> </li> <li> <div class="start"> <img src="https://images.unsplash.com/photo-1504579264001-833438f93df2?q=80&w=1738&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" alt="" /> </div> <div class="text"> <p>Headline</p> <p>Supporting text</p> </div> </li></ul>Video
-
Headline
Supporting text
13:37 -
Headline
Supporting text
90s
---import { List } from "@opui/astro"import { ListItem } from "@opui/astro"---
<List> <ListItem headline="Headline" description="Supporting text"> <video slot="start" controls muted> <source src="https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4" type="video/mp4" /> </video> <div slot="end">13:37</div> </ListItem> <ListItem headline="Headline" description="Supporting text"> <video slot="start" controls muted> <source src="https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4" type="video/mp4" /> </video> <div slot="end">90s</div> </ListItem></List><ul class="list"> <li> <div class="start"> <video controls muted> <source src="https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4" type="video/mp4" /> </video> </div> <div class="text"> <p>Headline</p> <p>Supporting text</p> </div> <div class="end"><div>13:37</div></div> </li> <li> <div class="start"> <video controls muted> <source src="https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4" type="video/mp4" /> </video> </div> <div class="text"> <p>Headline</p> <p>Supporting text</p> </div> <div class="end"><div>90s</div></div> </li></ul>End items
Authored via the end slot on ListItem.
Text
-
Headline
30kB -
Headline
Supporting text
99% -
Headline
100+
---import { List } from "@opui/astro"import { ListItem } from "@opui/astro"---
<List> <ListItem headline="Headline"> <div slot="end">30kB</div> </ListItem> <ListItem headline="Headline" description="Supporting text"> <div slot="end">99%</div> </ListItem> <ListItem headline="Headline"> <p slot="description"> Supporting text that truly is quite long enough to fill up multiple lines. </p> <div slot="end">100+</div> </ListItem></List><ul class="list"> <li> <div class="text"><p>Headline</p></div> <div class="end"><div>30kB</div></div> </li> <li> <div class="text"> <p>Headline</p> <p>Supporting text</p> </div> <div class="end"><div>99%</div></div> </li> <li> <div class="text"><p>Headline</p></div> <div class="end"><div>100+</div></div> </li></ul>Keyboard command
-
Save all
CTRL+ALT+DEL -
Save
CTRL+S
---import { List } from "@opui/astro"import { ListItem } from "@opui/astro"---
<List> <ListItem headline="Save all"> <div slot="end"><kbd>CTRL+ALT+DEL</kbd></div> </ListItem> <ListItem headline="Save"> <div slot="end"><kbd>CTRL+S</kbd></div> </ListItem></List><ul class="list"> <li> <div class="text"><p>Save all</p></div> <div class="end"> <div><kbd>CTRL+ALT+DEL</kbd></div> </div> </li> <li> <div class="text"><p>Save</p></div> <div class="end"> <div><kbd>CTRL+S</kbd></div> </div> </li></ul>Checkbox
Wrap the List item content with a <label class="checkbox" for="INPUTID">
to make the entire surface clickable.
Read more: Checkbox
---import { List } from "@opui/astro"import { ListItem } from "@opui/astro"import { CheckboxInput } from "@opui/astro"---
<List> <ListItem type="checkbox" for="checkbox-example-1"> <div slot="text">Checkbox 1</div> <CheckboxInput slot="end" id="checkbox-example-1" /> </ListItem> <ListItem type="checkbox" for="checkbox-example-2"> <div slot="text">Checkbox 2</div> <CheckboxInput slot="end" id="checkbox-example-2" /> </ListItem></List><script type="module"> function e(t = document) { t.querySelectorAll('input[type="checkbox"][data-indeterminate]').forEach( (n) => { n.indeterminate = !0; }, ); } function a() { (e(), document.addEventListener("astro:after-swap", () => e())); } a();</script><ul class="list"> <li> <label class="checkbox" for="checkbox-example-1"> <div class="text"><div>Checkbox 1</div></div> <div class="end"> <input type="checkbox" id="checkbox-example-1" slot="end" /> </div> </label> </li> <li> <label class="checkbox" for="checkbox-example-2"> <div class="text"><div>Checkbox 2</div></div> <div class="end"> <input type="checkbox" id="checkbox-example-2" slot="end" /> </div> </label> </li></ul>Radio
Wrap the List item content with a <label class="radio" for="INPUTID">
to make the entire surface clickable.
Radio group: Add a common name to each <input> for radio
group behavior.
Read more: Radio
---import { List } from "@opui/astro"import { ListItem } from "@opui/astro"import { RadioInput } from "@opui/astro"---
<List> <ListItem type="radio" for="radio-example-1"> <div slot="text">Radio 1</div> <RadioInput slot="end" id="radio-example-1" name="radio-example-group" /> </ListItem> <ListItem type="radio" for="radio-example-2"> <div slot="text">Radio 2</div> <RadioInput slot="end" id="radio-example-2" name="radio-example-group" /> </ListItem></List><ul class="list"> <li> <label class="radio" for="radio-example-1"> <div class="text"><div>Radio 1</div></div> <div class="end"> <input type="radio" id="radio-example-1" name="radio-example-group" slot="end" /> </div> </label> </li> <li> <label class="radio" for="radio-example-2"> <div class="text"><div>Radio 2</div></div> <div class="end"> <input type="radio" id="radio-example-2" name="radio-example-group" slot="end" /> </div> </label> </li></ul>Switch
Read more: Switch
---import { List } from "@opui/astro"import { ListItem } from "@opui/astro"import { SwitchInput } from "@opui/astro"---
<List> <ListItem type="switch" for="switch-example-1"> <div slot="text">Switch 1</div> <SwitchInput slot="end" id="switch-example-1" /> </ListItem> <ListItem type="switch" for="switch-example-2"> <div slot="text">Switch 2</div> <SwitchInput slot="end" id="switch-example-2" /> </ListItem></List><ul class="list"> <li> <label class="switch" for="switch-example-1"> <div class="text"><div>Switch 1</div></div> <div class="end"> <input type="checkbox" role="switch" id="switch-example-1" slot="end" /> </div> </label> </li> <li> <label class="switch" for="switch-example-2"> <div class="text"><div>Switch 2</div></div> <div class="end"> <input type="checkbox" role="switch" id="switch-example-2" slot="end" /> </div> </label> </li></ul>Inset
Enables a list item without a start icon to align with items that do.
-
No inset
-
Inset class
Makes the text line up nicely
-
Hidden
Inset class
Any
div.startwill be hidden when inset
---import { List } from "@opui/astro"import { ListItem } from "@opui/astro"---
<List> <ListItem headline="No inset"> <svg slot="start" xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32" > <path fill="currentColor" d="M16 16a7 7 0 1 0 0-14a7 7 0 0 0 0 14m-8.5 2A3.5 3.5 0 0 0 4 21.5v.5c0 2.393 1.523 4.417 3.685 5.793C9.859 29.177 12.802 30 16 30s6.14-.823 8.315-2.207C26.477 26.417 28 24.393 28 22v-.5a3.5 3.5 0 0 0-3.5-3.5z" ></path> </svg> </ListItem> <ListItem inset headline="Inset class"> <p>Makes the text line up nicely</p> </ListItem> <ListItem inset headline="Inset class"> <div slot="start">Hidden</div> <p>Any <code>div.start</code> will be hidden when inset</p> </ListItem></List><ul class="list"> <li> <div class="start"> <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32" > <path fill="currentColor" d="M16 16a7 7 0 1 0 0-14a7 7 0 0 0 0 14m-8.5 2A3.5 3.5 0 0 0 4 21.5v.5c0 2.393 1.523 4.417 3.685 5.793C9.859 29.177 12.802 30 16 30s6.14-.823 8.315-2.207C26.477 26.417 28 24.393 28 22v-.5a3.5 3.5 0 0 0-3.5-3.5z" ></path> </svg> </div> <div class="text"><p>No inset</p></div> </li> <li class="inset"> <div class="text"> <p>Inset class</p> <p>Makes the text line up nicely</p> </div> </li> <li class="inset"> <div class="start"><div>Hidden</div></div> <div class="text"> <p>Inset class</p> <p>Any <code>div.start</code> will be hidden when inset</p> </div> </li></ul>Gutterless
Apply the .gutterless class on the ul.list element
to remove the inline padding on the list items.
-
Gutterless list item
-
Headline
Supporting text
100+
---import { List } from "@opui/astro"import { ListItem } from "@opui/astro"---
<List gutterless> <ListItem headline="Gutterless list item"> <div slot="end"> <button class="icon-button" type="button"> <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32" > <path fill="currentColor" d="M12 12h2v12h-2zm6 0h2v12h-2zM6 28h20V10H6zm16-22V4H10v2H4v2h24V6zM12 4h8v2h-8z" ></path> </svg> </button> </div> </ListItem> <ListItem headline="Headline" description="Supporting text"> <svg slot="start" xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32" > <path fill="currentColor" d="M16 16a7 7 0 1 0 0-14a7 7 0 0 0 0 14m-8.5 2A3.5 3.5 0 0 0 4 21.5v.5c0 2.393 1.523 4.417 3.685 5.793C9.859 29.177 12.802 30 16 30s6.14-.823 8.315-2.207C26.477 26.417 28 24.393 28 22v-.5a3.5 3.5 0 0 0-3.5-3.5z" ></path> </svg> <div slot="end">100+</div> </ListItem></List><ul class="list gutterless"> <li> <div class="text"><p>Gutterless list item</p></div> <div class="end"> <div> <button class="icon-button" type="button"> <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32" > <path fill="currentColor" d="M12 12h2v12h-2zm6 0h2v12h-2zM6 28h20V10H6zm16-22V4H10v2H4v2h24V6zM12 4h8v2h-8z" ></path> </svg> </button> </div> </div> </li> <li> <div class="start"> <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32" > <path fill="currentColor" d="M16 16a7 7 0 1 0 0-14a7 7 0 0 0 0 14m-8.5 2A3.5 3.5 0 0 0 4 21.5v.5c0 2.393 1.523 4.417 3.685 5.793C9.859 29.177 12.802 30 16 30s6.14-.823 8.315-2.207C26.477 26.417 28 24.393 28 22v-.5a3.5 3.5 0 0 0-3.5-3.5z" ></path> </svg> </div> <div class="text"> <p>Headline</p> <p>Supporting text</p> </div> <div class="end"><div>100+</div></div> </li></ul>Borders
On every item
Apply the .bordered class on the ul.list element to
give all list items a border.
-
So
-
Many
-
Borders
---import { List } from "@opui/astro"import { ListItem } from "@opui/astro"---
<List bordered> <ListItem headline="So" /> <ListItem headline="Many" /> <ListItem headline="Borders" /></List><ul class="list bordered"> <li> <div class="text"><p>So</p></div> </li> <li> <div class="text"><p>Many</p></div> </li> <li> <div class="text"><p>Borders</p></div> </li></ul>On one item
Apply the .border-top class on a li.list item to give
it an upper border.
-
I need borders
-
Help
-
Thanks
---import { List } from "@opui/astro"import { ListItem } from "@opui/astro"---
<List> <ListItem headline="I need borders" /> <ListItem headline="Help" /> <ListItem borderTop headline="Thanks" /></List><ul class="list"> <li> <div class="text"><p>I need borders</p></div> </li> <li> <div class="text"><p>Help</p></div> </li> <li class="border-top"> <div class="text"><p>Thanks</p></div> </li></ul>Dense
Just add the dense prop to the List!
-
Headline
-
Headline
Supporting text that truly is quite long enough to fill up multiple lines.
-
Trailing supporting text
100+ -
Trailing keyboard command
CTRL+Shift+X -
Headline with start icon
-
Headline with start icon
Supporting text that truly is quite long enough to fill up multiple lines.
-
Inset class
Makes the text line up nicely
-
Link list item
-
OP
Headline
-
Headline
Supporting text
-
Link with start icon
-
End icon button
<List dense> <!-- --></List><script type="module"> function c() { const s = document.querySelectorAll(".list-dense-toggle input"), o = document.querySelectorAll(".list-dense-target"); function n(e) { (o.forEach((t) => { e ? t.classList.add("dense") : t.classList.remove("dense"); }), s.forEach((t) => { t.checked !== e && (t.checked = e); })); } (s.forEach((e) => { e.addEventListener("change", () => n(e.checked)); }), s.length > 0 && n(s[0].checked)); } c(); document.addEventListener("astro:after-swap", c);</script><ul class="list dense list-dense-target"> <li> <div class="text"><p>Headline</p></div> </li> <li> <div class="text"> <p>Headline</p> <p> Supporting text that truly is quite long enough to fill up multiple lines. </p> </div> </li> <li> <div class="text"><p>Trailing supporting text</p></div> <div class="end"><div>100+</div></div> </li> <li> <div class="text"><p>Trailing keyboard command</p></div> <div class="end"> <div><kbd>CTRL+Shift+X</kbd></div> </div> </li> <li class="border-top"> <div class="start"> <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32" > <path fill="currentColor" d="M16 16a7 7 0 1 0 0-14a7 7 0 0 0 0 14m-8.5 2A3.5 3.5 0 0 0 4 21.5v.5c0 2.393 1.523 4.417 3.685 5.793C9.859 29.177 12.802 30 16 30s6.14-.823 8.315-2.207C26.477 26.417 28 24.393 28 22v-.5a3.5 3.5 0 0 0-3.5-3.5z" ></path> </svg> </div> <div class="text"><p>Headline with start icon</p></div> </li> <li> <div class="start"> <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32" > <path fill="currentColor" d="M16 16a7 7 0 1 0 0-14a7 7 0 0 0 0 14m-8.5 2A3.5 3.5 0 0 0 4 21.5v.5c0 2.393 1.523 4.417 3.685 5.793C9.859 29.177 12.802 30 16 30s6.14-.823 8.315-2.207C26.477 26.417 28 24.393 28 22v-.5a3.5 3.5 0 0 0-3.5-3.5z" ></path> </svg> </div> <div class="text"> <p>Headline with start icon</p> <p> Supporting text that truly is quite long enough to fill up multiple lines. </p> </div> </li> <li class="inset"> <div class="text"> <p>Inset class</p> <p>Makes the text line up nicely</p> </div> </li> <li class="border-top"> <button> <div class="text"><p>Button list item</p></div> </button> </li> <li> <a href="#"> <div class="text"><p>Link list item</p></div> </a> </li> <li class="border-top"> <div class="start"><div slot="start" class="avatar">OP</div></div> <div class="text"><p>Headline</p></div> </li> <li> <div class="start"> <div slot="start" class="avatar"> <img src="https://images.unsplash.com/photo-1614530606961-c4ce986825c1?q=80&w=1827&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" alt="" /> </div> </div> <div class="text"> <p>Headline</p> <p>Supporting text</p> </div> </li> <li class="border-top"> <button> <div class="start"> <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 28 28" > <path fill="currentColor" d="M21.75 3A3.25 3.25 0 0 1 25 6.25v15.5A3.25 3.25 0 0 1 21.75 25H6.25A3.25 3.25 0 0 1 3 21.75V6.25A3.25 3.25 0 0 1 6.25 3zm0 1.5H6.25A1.75 1.75 0 0 0 4.5 6.25V15h6a.75.75 0 0 1 .743.648l.007.102a2.75 2.75 0 1 0 5.5 0a.75.75 0 0 1 .648-.743L17.5 15h6V6.25a1.75 1.75 0 0 0-1.75-1.75" ></path> </svg> </div> <div class="text"><p>Button with start icon</p></div> </button> </li> <li> <a href="#"> <div class="start"> <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24" > <path fill="currentColor" d="m13.94 5l5.061 5.06L9.063 20a2.25 2.25 0 0 1-1 .58l-5.115 1.395a.75.75 0 0 1-.92-.921l1.394-5.116a2.25 2.25 0 0 1 .58-.999zm-7.414 6l-1.5 1.5H2.75a.75.75 0 0 1 0-1.5zm14.352-8.174l.153.144l.145.153a3.58 3.58 0 0 1-.145 4.908l-.97.969L15 3.94l.97-.97a3.58 3.58 0 0 1 4.908-.144M10.526 7l-1.5 1.5H2.75a.75.75 0 1 1 0-1.5zm4-4l-1.5 1.5H2.75a.75.75 0 1 1 0-1.5z" ></path> </svg> </div> <div class="text"><p>Link with start icon</p></div> </a> </li> <li class="border-top"> <button> <div class="text"><p>End icon</p></div> <div class="end"> <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32" > <path fill="currentColor" d="M11.116 26.634a1.25 1.25 0 0 1 0-1.768L19.982 16l-8.866-8.866a1.25 1.25 0 0 1 1.768-1.768l9.75 9.75a1.25 1.25 0 0 1 0 1.768l-9.75 9.75a1.25 1.25 0 0 1-1.768 0" ></path> </svg> </div> </button> </li> <li> <div class="text"><p>End icon button</p></div> <div class="end"> <div> <button class="icon-button"> <span class="sr-only">More</span> <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32" > <path fill="currentColor" d="M16 9.5a2.5 2.5 0 1 1 0-5a2.5 2.5 0 0 1 0 5m0 9a2.5 2.5 0 1 1 0-5a2.5 2.5 0 0 1 0 5M13.5 25a2.5 2.5 0 1 0 5 0a2.5 2.5 0 0 0-5 0" ></path> </svg> </button> </div> </div> </li> <li class="border-top"> <label class="checkbox" for="dense-checkbox-all"> <div class="text"><div>Checkbox</div></div> <div class="end"> <input type="checkbox" id="dense-checkbox-all" slot="end" /> </div> </label> </li> <li class="border-top"> <label class="radio" for="dense-radio-all-1"> <div class="text"><div>Radio 1</div></div> <div class="end"> <input type="radio" id="dense-radio-all-1" name="dense-radio-group-all" slot="end" /> </div> </label> </li> <li> <label class="radio" for="dense-radio-all-2"> <div class="text"><div>Radio 2</div></div> <div class="end"> <input type="radio" id="dense-radio-all-2" name="dense-radio-group-all" slot="end" /> </div> </label> </li> <li class="border-top"> <label class="switch" for="dense-switch-all-1"> <div class="text"><div>Switch 1</div></div> <div class="end"> <input type="checkbox" role="switch" id="dense-switch-all-1" slot="end" /> </div> </label> </li></ul>Anatomy
- Container:
ul.list - List item:
li -
Content wrapper (optional):
a,button,label -
Start content (optional):
.start>svg,img,video -
Text content:
.text>p,p + p -
End content (optional):
.end>svg,p,button,a,input
-
Headline
Supporting text
100+
API
List
| Prop | Type | Default | Description |
|---|---|---|---|
bordered | boolean | false | Adds a border between list items. |
dense | boolean | false | Packs the list tighter. |
gutterless | boolean | false | Removes list inline padding. |
variant | "default" | "tonal" | "transparent" | - | The background color variant of the list. |
List item
| Prop | Type | Default | Description |
|---|---|---|---|
as | string | - | Renders an inner element of the given tag (e.g. "a", "button"). All extra props are forwarded to it, so any HTML attribute is
supported without enumeration. |
borderTop | boolean | false | List item top border. |
headline | string | - | The main text for the list item. |
inset | boolean | false | Aligns with items that have a start icon. |
supportingText | string | - | Extra text displayed below the headline. |
Browser support
See also the full browser support guide.
Installation
@layer components.extended {
/*Lists meant to be used stand-alone or as part of Select elements
Intended use-case: - ul.list > li - .select > .list > option*/ :where(.list) { --_bg-color: var(--surface-filled); --_bg-color-hover: oklch(from var(--primary) l c h / 15%);
background-color: var(--_bg-color); color: var(--text-primary); list-style: none; padding: var(--size-2) 0;
@media (pointer: coarse) {
&, * { user-select: none; } }
/* Background color */ &.transparent { --_bg-color: transparent; }
&.default { --_bg-color: var(--surface-default); }
&.tonal { --_bg-color: var(--surface-tonal); }
/* Borders on all list items */ &.bordered { :where(li + li, option + option) { margin-block-start: var(--size-3);
&::before { block-size: 1px; border-block-start: 1px solid var(--border-color); content: ""; display: block; inline-size: 100%; inset: calc(-1 * var(--size-2)) 0 auto 0; position: absolute; visibility: visible; /* override select > option:before style */ } } }
/* Dense - less gaps and spacing */ &.dense { :where(li, option) { gap: var(--size-2); min-block-size: var(--size-7); padding: var(--size-1) var(--size-2);
&.border-top { margin-block-start: var(--size-2);
&::before { inset: calc(-1 * var(--size-1)) 0 auto 0; } }
/* Clickable list item */ &:has(> a, > button, > label) { min-block-size: auto; padding: 0; }
&> :where(a, button, label) { gap: var(--size-2); min-block-size: var(--size-7); padding: var(--size-1) var(--size-2); }
/* Checkbox / Radio */ &>label { .end { padding-inline-end: 0.125rem; }
&:where(.checkbox, .radio) { --_input-size: var(--size-3); } }
/* Leading and trailing content */ .start, .end { .avatar { max-inline-size: var(--size-6); }
.icon-button, svg { max-inline-size: var(--size-4); }
.checkbox, .radio { --_input-size: var(--size-3); } } } }
/* Gutterless */ &.gutterless { :where(li, option) { padding-inline: 0;
&> :where(a, button, label) { padding-inline: 0; } } }
/* List item */ :where(li, option, [role="group"] > label) { align-items: center; background-color: var(--_bg-color); display: flex; font-size: var(--font-size-05); gap: var(--size-3); isolation: isolate; min-block-size: 40px; padding: var(--size-2) var(--size-3); position: relative;
&::before { display: none; /* removing checkmark from option */ }
* { font-size: inherit; }
/* Clickable list item */ &:has(> a, > button, > label) { background: transparent; display: block; min-block-size: auto; padding: 0;
&[aria-selected="true"]> :where(a, button, label), &[aria-selected="true"], &:has(> [aria-current="page"])> :where(a, button, label) { background-color: var(--_bg-color-hover); } }
/* Select option */ &:where(option) { align-items: center; background-color: var(--_bg-color); color: inherit; cursor: pointer; display: flex; gap: var(--size-3); inline-size: 100%; margin: 0; min-block-size: 40px; padding: var(--size-2) var(--size-3); text-align: start; text-decoration: none; z-index: 0;
&:hover { background-color: var(--_bg-color-hover); }
&[aria-selected="true"] { background-color: var(--_bg-color-hover); color: var(--primary); }
&:checked { background-color: oklch(from var(--primary) l c h / 30%); } }
&>a, &>button, &>label { align-items: center; background-color: var(--_bg-color); color: inherit; cursor: pointer; display: flex; gap: var(--size-3); inline-size: 100%; margin: 0; min-block-size: 40px; outline-offset: -3px; padding: var(--size-2) var(--size-3); text-align: start; text-decoration: none; z-index: 0;
&:hover { background-color: var(--_bg-color-hover); }
&[aria-selected="true"], &[aria-current="page"] { background-color: var(--_bg-color-hover); } }
/* Checkbox / Radio / Switch */ &>label { &:where(.checkbox, .radio, .switch) { display: flex; }
.end { padding-inline-end: var(--size-1); }
&:where(.checkbox, .radio) { inline-size: 100%; }
/* Switches look comically big in lists, so it's better to use a smaller variant */ &.switch { --_dot-size: 0.75rem; --_track-height: var(--size-4); --_track-width: 2.5rem; } }
/* Video */ &:has(video) { padding: 0.75rem var(--size-3) 0.75rem 0; }
/* Border between list items */ &.border-top { margin-block-start: var(--size-3);
&::before { block-size: 1px; border-block-start: 1px solid var(--border-color); content: ""; display: block; inline-size: 100%; inset: calc(-1 * var(--size-2)) 0 auto 0; position: absolute; } }
/* Text */ .text { flex: 1; line-height: 1.6;
:where(h1, h2, h3, h4, h5, h6, p, span) { color: inherit; font-weight: 400; }
p+p { color: var(--text-muted); font-size: var(--font-size-0); } }
/* Leading content */ .start { align-items: center; align-self: center; display: grid; z-index: 1;
&:has(svg) { max-inline-size: var(--size-5); }
svg { padding-block-start: 0.125rem; }
img { aspect-ratio: 1; inline-size: 56px; object-fit: cover; }
video { aspect-ratio: 16/9; block-size: 64px; object-fit: cover; } }
/* Trailing content */ .end { align-items: center; display: flex; font-size: var(--font-size-0); text-align: end; z-index: 1;
&:not(:has(a, button, input)) { pointer-events: none; }
kbd { background-color: transparent; border: 0; color: inherit; opacity: 0.6; }
svg { inline-size: 100%; max-inline-size: var(--size-5); } }
/* Inset */ &.inset { .text { padding-inline-start: calc(var(--size-5) + var(--size-3)); }
/* Safety measure so it won't look bad if there for some reason should exist a leading element inside. */ .start { display: none; } } } }}