Skip to content

List

Configurations

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?

All

  • Headline

  • Headline

    Supporting text that truly is quite long enough to fill up multiple lines.

  • Trailing supporting text

    100+
  • 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

  • Link with start icon

  • End icon button

Text-only

  • Headline

  • Headline

    Supporting text

    42kb
  • Headline

    Supporting text that truly is quite long enough to fill up multiple lines.

    999+

Icon

  • Headline

  • Headline

    Supporting text

    100+

Avatar

Image

  • Headline

    Supporting text

    100+
  • Headline

    Supporting text

    100+

Video

  • Headline

    Supporting text

    100+
  • Headline

    Supporting text

    100+

Checkbox

Wrap the List item content with a <label for="INPUTID"> to make the entire surface clickable.

html
<li>
  <label for="checkbox-example-1">
    <div class="text"></div>
    <div class="end">
      <input class="checkbox" id="checkbox-example-1" type="checkbox" />
    </div>
  </label>
</li>

Radio

Wrap the List item content with a <label for="INPUTID"> to make the entire surface clickable. Add a common name to each <input> for radio group behavior.

Switch

Inset

  • No inset

  • Inset class

    Makes the text line up nicely

  • Inset class

    Any div.start will be hidden when inset

Borders

On every item

Apply the .bordered class on the ul.list element to give all list items a border.

  • Headline

  • Headline

  • Headline

html
<ul class="list bordered">
  <!--  -->
</ul>

On one item

Apply the .border-top class on a li.list item to give it an upper border.

  • Help

  • I need borders

  • Thanks

html
<ul class="list">
  <li></li>
  <li></li>
  <li class="border-top"></li>
</ul>

Anatomy

  1. Container: ul.list
  2. List item: li
  3. Content wrapper (optional): a, button, label
  4. Start content (optional): .start > svg, img, video
  5. Text content: .text > p, p + p
  6. End content (optional): .end > svg, p, button, a, input
  • Text

    15%
html
<ul class="list">
  <li>
    <button>
      <div class="start"></div>
      <div class="text"></div>
      <div class="end"></div>
    </button>
  </li>
</ul>

API

Browser compatibility

Installation

css
:where(ul.list) {
  --_bg-color: light-dark(var(--gray-0), var(--gray-14));

  background-color: var(--_bg-color);
  list-style: none;
  padding: var(--size-2) 0;

  /* Borders on all list items */
  &.bordered {
    li + li {
      margin-block-start: var(--size-3);
      &:before {
        block-size: 1px;
        border-block-start: 1px solid var(--border-color);
        content: "";
        inline-size: 100%;
        inset: calc(0rem - var(--size-2)) 0 auto 0;
        position: absolute;
      }
    }
  }

  li {
    align-items: center;
    background: var(--_bg-color) var(--ripple, none);
    display: flex;
    font-size: var(--font-size-sm);
    gap: var(--size-3);
    isolation: isolate;
    min-block-size: 40px;
    padding: var(--size-2) var(--size-3);
    position: relative;

    * {
      font-size: inherit;
    }

    /* Clickable list item */
    &:has(> a, > button, > label) {
      background: transparent;
      display: block;
      padding: 0;
    }

    & > a,
    & > button,
    & > label {
      align-items: center;
      background: var(--_bg-color) var(--ripple, none);
      color: inherit;
      cursor: pointer;
      display: flex;
      gap: var(--size-3);
      inline-size: 100%;
      margin: 0;
      padding: var(--size-2) var(--size-3);
      text-align: start;
      text-decoration: none;
      z-index: 0;

      /*** Ripple effect */
      background-position: center;
      transition: background 0.8s;
      &:where(:not(:active):hover) {
        --ripple: radial-gradient(circle, transparent 1%, var(--_bg-color) 1%)
          center/15000%;
      }

      &:where(:hover:active) {
        background-size: var(--button-ripple-size);
        transition: background 0s;
      }

      &:hover {
        background-color: light-dark(var(--gray-1), var(--gray-13));
      }

      /*** Remove ripple effect when trailing button is clicked */
      &:has(.end:hover) {
        &:where(:not(:active):hover) {
          --ripple: none;
        }
      }
    }

    /* Video */
    &:has(video) {
      padding: 0.75rem var(--size-3) 0.75rem 0;
    }

    /* Border */
    &.border-top {
      margin-block-start: var(--size-3);
      &:before {
        block-size: 1px;
        border-block-start: 1px solid var(--border-color);
        content: "";
        inline-size: 100%;
        inset: calc(0rem - var(--size-2)) 0 auto 0;
        position: absolute;
      }
    }

    /* Text */
    .text {
      flex: 1;

      p + p {
        font-size: var(--font-size-xs);
      }
    }

    /* Leading content */
    .start {
      align-self: start;
      z-index: 1;

      &:has(svg) {
        max-inline-size: var(--size-5);
      }

      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-xs);
      z-index: 1;

      &:not(:has(a, button, input)) {
        pointer-events: none;
      }

      svg {
        max-inline-size: var(--size-5);
        inline-size: 100%;
      }
    }

    /* 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;
      }
    }
  }
}