Skip to content

ExpressiveCSS

Browse documentation

Lists

Continuous vertical indexes of text and images.

View as Markdown

A .list is a vertical index. Use it so people can find an item and act on it. The component is HTML only, with no JavaScript. Order items logically, keep each row short, and put icons, text, and actions in the same place on every row.

The leading visual is the first icon, <img>, or <input>. The trailing action is the last icon, <kbd>, <button>, <time>, or .meta. A following <p> is supporting text. Mark the current row with active or selected. Do not use aria-selected, which is not valid on a plain listitem. It needs a role (option, tab, row, treeitem), and each of those brings a keyboard contract with it.

There are two variants: standard (the default) and segmented.

Standard

Standard rows are transparent. The selected item is a pill in secondary-container. The leading icon fills when the row is selected.

  • List item ⌘C
  • List item ⌘C
  • List item ⌘C
  • List item ⌘C
  • List item ⌘C
  • List item ⌘C
<ul class="list">
  <li>
    <span class="material-symbols" aria-hidden="true">star</span>
    List item
    <kbd>⌘C</kbd>
  </li>
  <li class="selected">
    <span class="material-symbols" aria-hidden="true">star</span>
    List item
    <kbd>⌘C</kbd>
  </li>
</ul>

Segmented

Add segmented to make every row a rounded tile. The selected tile uses the same secondary-container fill. Use this when the list itself should read as a group.

  • List item ⌘C
  • List item ⌘C
  • List item ⌘C
  • List item ⌘C
  • List item ⌘C
  • List item ⌘C
<ul class="list segmented">
  <li class="selected">
    <span class="material-symbols" aria-hidden="true">star</span>
    List item
    <kbd>⌘C</kbd>
  </li>
</ul>

Text

A row can be a single line of label text, or a label with supporting text underneath. Either can wrap. To cut it off with an ellipsis instead, add truncate.

  • Label text only
  • A long label that will not wrap past the end of the row and is cut off
  • Headline

    Supporting text sits under the label and can wrap onto another line.

<li>Label text only</li>
<li><span class="truncate">A long label…</span></li>
<li>
  Headline
  <p>Supporting text sits under the label.</p>
</li>

Icons

A leading icon is a quick visual cue for the label. A trailing icon shows status or an action, such as more, info, or a chevron.

  • Inbox
  • Sent
  • Trash
<li>
  <span class="material-symbols" aria-hidden="true">inbox</span>
  Inbox
  <span class="material-symbols" aria-hidden="true">chevron_right</span>
</li>

Avatars and images

A leading .circle is a 40dp avatar. A bare <img> is a 56dp thumbnail.

  • Ada Lovelace

    Mathematician

  • Icon avatar
  • Thumbnail

    56dp image, 8dp corners.

<li>
  <img src="portrait.jpg" alt="" class="circle">
  Ada Lovelace
  <p>Mathematician</p>
</li>

Sortable

Add sortable to a list to let people reorder it. Drag an item by its .drag-handle, or press its [data-move="up"] and [data-move="down"] buttons. Every item needs both buttons, in the row or in a menu. They are the keyboard path and the single-pointer alternative to dragging. Escape during a drag puts the item back.

Listen for change to save the order. After each move, the list fires a bubbling change event whose detail is { item, from, to }. A polite live region announces the new position (Expressive.Sortable.i18n.moved). The root bundle starts the behavior on load. With the modular entry, call Sortable.Init().

  • Blue in Green
  • So What
  • Freddie Freeloader
<ul class="list sortable" aria-label="Playlist">
  <li>
    <span class="drag-handle" aria-hidden="true"></span>
    <span>Blue in Green</span>
    <span class="sortable-actions">
      <button type="button" class="icon-button" data-move="up" aria-label="Move Blue in Green up">
        <span class="material-symbols" aria-hidden="true">arrow_upward</span>
      </button>
      <button type="button" class="icon-button" data-move="down" aria-label="Move Blue in Green down">
        <span class="material-symbols" aria-hidden="true">arrow_downward</span>
      </button>
    </span>
  </li>
  …
</ul>

Long lists

Add defer-render to a long list so the browser skips layout and paint for rows that are off screen. Each direct child gets content-visibility: auto. It works on any container whose children stack, such as a column of cards.

Off-screen rows stay in the page. Screen readers and find-in-page still reach their text, and a script can still query them. A skipped row reserves --defer-render-row-size, which defaults to the list item height inside a .list and to 3.5rem elsewhere. Once a row has been on screen, the browser remembers its real height. Set the property to the average row height when rows differ, so the scrollbar stays close to the list's real length.

  • Invoice 1
  • Invoice 2
  • Invoice 3
  • Invoice 4
  • Invoice 5
  • Invoice 6
  • Invoice 7
  • Invoice 8
  • Invoice 9
  • Invoice 10
  • Invoice 11
  • Invoice 12
  • Invoice 13
  • Invoice 14
  • Invoice 15
  • Invoice 16
  • Invoice 17
  • Invoice 18
  • Invoice 19
  • Invoice 20
  • Invoice 21
  • Invoice 22
  • Invoice 23
  • Invoice 24
  • Invoice 25
  • Invoice 26
  • Invoice 27
  • Invoice 28
  • Invoice 29
  • Invoice 30
  • Invoice 31
  • Invoice 32
  • Invoice 33
  • Invoice 34
  • Invoice 35
  • Invoice 36
  • Invoice 37
  • Invoice 38
  • Invoice 39
  • Invoice 40
  • Invoice 41
  • Invoice 42
  • Invoice 43
  • Invoice 44
  • Invoice 45
  • Invoice 46
  • Invoice 47
  • Invoice 48
  • Invoice 49
  • Invoice 50
  • Invoice 51
  • Invoice 52
  • Invoice 53
  • Invoice 54
  • Invoice 55
  • Invoice 56
  • Invoice 57
  • Invoice 58
  • Invoice 59
  • Invoice 60
  • Invoice 61
  • Invoice 62
  • Invoice 63
  • Invoice 64
  • Invoice 65
  • Invoice 66
  • Invoice 67
  • Invoice 68
  • Invoice 69
  • Invoice 70
  • Invoice 71
  • Invoice 72
  • Invoice 73
  • Invoice 74
  • Invoice 75
  • Invoice 76
  • Invoice 77
  • Invoice 78
  • Invoice 79
  • Invoice 80
  • Invoice 81
  • Invoice 82
  • Invoice 83
  • Invoice 84
  • Invoice 85
  • Invoice 86
  • Invoice 87
  • Invoice 88
  • Invoice 89
  • Invoice 90
  • Invoice 91
  • Invoice 92
  • Invoice 93
  • Invoice 94
  • Invoice 95
  • Invoice 96
  • Invoice 97
  • Invoice 98
  • Invoice 99
  • Invoice 100
  • Invoice 101
  • Invoice 102
  • Invoice 103
  • Invoice 104
  • Invoice 105
  • Invoice 106
  • Invoice 107
  • Invoice 108
  • Invoice 109
  • Invoice 110
  • Invoice 111
  • Invoice 112
  • Invoice 113
  • Invoice 114
  • Invoice 115
  • Invoice 116
  • Invoice 117
  • Invoice 118
  • Invoice 119
  • Invoice 120
  • Invoice 121
  • Invoice 122
  • Invoice 123
  • Invoice 124
  • Invoice 125
  • Invoice 126
  • Invoice 127
  • Invoice 128
  • Invoice 129
  • Invoice 130
  • Invoice 131
  • Invoice 132
  • Invoice 133
  • Invoice 134
  • Invoice 135
  • Invoice 136
  • Invoice 137
  • Invoice 138
  • Invoice 139
  • Invoice 140
  • Invoice 141
  • Invoice 142
  • Invoice 143
  • Invoice 144
  • Invoice 145
  • Invoice 146
  • Invoice 147
  • Invoice 148
  • Invoice 149
  • Invoice 150
  • Invoice 151
  • Invoice 152
  • Invoice 153
  • Invoice 154
  • Invoice 155
  • Invoice 156
  • Invoice 157
  • Invoice 158
  • Invoice 159
  • Invoice 160
  • Invoice 161
  • Invoice 162
  • Invoice 163
  • Invoice 164
  • Invoice 165
  • Invoice 166
  • Invoice 167
  • Invoice 168
  • Invoice 169
  • Invoice 170
  • Invoice 171
  • Invoice 172
  • Invoice 173
  • Invoice 174
  • Invoice 175
  • Invoice 176
  • Invoice 177
  • Invoice 178
  • Invoice 179
  • Invoice 180
  • Invoice 181
  • Invoice 182
  • Invoice 183
  • Invoice 184
  • Invoice 185
  • Invoice 186
  • Invoice 187
  • Invoice 188
  • Invoice 189
  • Invoice 190
  • Invoice 191
  • Invoice 192
  • Invoice 193
  • Invoice 194
  • Invoice 195
  • Invoice 196
  • Invoice 197
  • Invoice 198
  • Invoice 199
  • Invoice 200
  • Invoice 201
  • Invoice 202
  • Invoice 203
  • Invoice 204
  • Invoice 205
  • Invoice 206
  • Invoice 207
  • Invoice 208
  • Invoice 209
  • Invoice 210
  • Invoice 211
  • Invoice 212
  • Invoice 213
  • Invoice 214
  • Invoice 215
  • Invoice 216
  • Invoice 217
  • Invoice 218
  • Invoice 219
  • Invoice 220
  • Invoice 221
  • Invoice 222
  • Invoice 223
  • Invoice 224
  • Invoice 225
  • Invoice 226
  • Invoice 227
  • Invoice 228
  • Invoice 229
  • Invoice 230
  • Invoice 231
  • Invoice 232
  • Invoice 233
  • Invoice 234
  • Invoice 235
  • Invoice 236
  • Invoice 237
  • Invoice 238
  • Invoice 239
  • Invoice 240
  • Invoice 241
  • Invoice 242
  • Invoice 243
  • Invoice 244
  • Invoice 245
  • Invoice 246
  • Invoice 247
  • Invoice 248
  • Invoice 249
  • Invoice 250
  • Invoice 251
  • Invoice 252
  • Invoice 253
  • Invoice 254
  • Invoice 255
  • Invoice 256
  • Invoice 257
  • Invoice 258
  • Invoice 259
  • Invoice 260
  • Invoice 261
  • Invoice 262
  • Invoice 263
  • Invoice 264
  • Invoice 265
  • Invoice 266
  • Invoice 267
  • Invoice 268
  • Invoice 269
  • Invoice 270
  • Invoice 271
  • Invoice 272
  • Invoice 273
  • Invoice 274
  • Invoice 275
  • Invoice 276
  • Invoice 277
  • Invoice 278
  • Invoice 279
  • Invoice 280
  • Invoice 281
  • Invoice 282
  • Invoice 283
  • Invoice 284
  • Invoice 285
  • Invoice 286
  • Invoice 287
  • Invoice 288
  • Invoice 289
  • Invoice 290
  • Invoice 291
  • Invoice 292
  • Invoice 293
  • Invoice 294
  • Invoice 295
  • Invoice 296
  • Invoice 297
  • Invoice 298
  • Invoice 299
  • Invoice 300
<ul class="list defer-render" aria-label="Invoices" style="--defer-render-row-size: 4.5rem">
  <li>…</li>
  …
</ul>

The browser clips anything a deferred row draws outside its box. Focus rings inside a row are drawn inset for that reason, but a box shadow or a badge that hangs over the edge is cut off. Each row is also the containing block for absolutely and fixed positioned content. Menus and tooltips open in the top layer and are not affected. Rows with .swipe-actions are never deferred, because their actions wait past the row's end.

Tables are not supported. Browsers ignore content-visibility on table rows. When the rows are not in the page at all, such as a dataset too large to render as markup or one fetched a page at a time during scrolling, use a virtual list script instead. ExpressiveCSS does not ship one.

Tokens

Set these on the list (or on :root):

Token Default
--md-comp-list-item-height56px
--md-comp-list-item-two-line-height72px
--md-comp-list-item-padding16px
--md-comp-list-item-shape100px (20px segmented)
--md-comp-list-item-container-colortransparent
--md-comp-list-selected-container-color--md-sys-color-secondary-container
--md-comp-list-leading-icon-size24px
--md-comp-list-segmented-gap2px

No matching pages

  • Source color

    The seed every generated ramp derives from. Pick one and browse the docs — the whole theme follows. Error does not: it is a fixed hue.

    Default source: Mist Iris.

    ExpressiveCSS presets: Lagoon, Harbor, Breeze, Cobalt, Iris.

    Bold

    Soft

    Mist

    Glow