List

One- to three-line list items with leading and trailing slots.

M3: Lists

  • StorageAuto-backupBack up new media over Wi-Fi

Installation

npx shadcn@latest add https://mountainview-ui.baltoon.jp/r/list.json

Requires the theme tokens — see Installation if this is your first component.

Source

"use client";

import * as React from "react";

import { cn } from "@/lib/utils";

/**
 * M3 lists: one/two/three-line items with leading icons, avatars,
 * and trailing supporting text.
 */
function List({ className, ...props }: React.ComponentProps<"ul">) {
  return (
    <ul
      data-slot="list"
      className={cn("flex w-full flex-col bg-surface py-2", className)}
      {...props}
    />
  );
}

function ListItem({
  className,
  leading,
  overline,
  headline,
  supportingText,
  trailing,
  onClick,
  ...props
}: Omit<React.ComponentProps<"li">, "onClick"> & {
  /** Leading icon or avatar. */
  leading?: React.ReactNode;
  overline?: React.ReactNode;
  headline: React.ReactNode;
  supportingText?: React.ReactNode;
  /** Trailing icon, meta text, or control. */
  trailing?: React.ReactNode;
  onClick?: React.MouseEventHandler<HTMLElement>;
}) {
  const interactive = Boolean(onClick);
  const Inner = interactive ? "button" : "div";

  return (
    <li data-slot="list-item" className={cn("flex", className)} {...props}>
      <Inner
        {...(interactive ? { type: "button" as const, onClick } : {})}
        className={cn(
          "flex min-h-14 w-full items-center gap-4 px-4 py-2 text-left outline-none",
          interactive &&
            "cursor-pointer transition-colors state-on-surface focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-primary"
        )}
      >
        {leading ? (
          <span className="flex shrink-0 items-center justify-center text-on-surface-variant [&_svg]:size-6">
            {leading}
          </span>
        ) : null}
        <span className="flex min-w-0 flex-1 flex-col">
          {overline ? (
            <span className="text-label-m text-on-surface-variant">{overline}</span>
          ) : null}
          <span className="truncate text-body-l text-on-surface">{headline}</span>
          {supportingText ? (
            <span className="line-clamp-2 text-body-m text-on-surface-variant">
              {supportingText}
            </span>
          ) : null}
        </span>
        {trailing ? (
          <span className="flex shrink-0 items-center text-label-s text-on-surface-variant [&_svg]:size-6">
            {trailing}
          </span>
        ) : null}
      </Inner>
    </li>
  );
}

function ListDivider({ className, ...props }: React.ComponentProps<"li">) {
  return (
    <li
      data-slot="list-divider"
      role="separator"
      className={cn("my-0 ml-4 h-px bg-outline-variant", className)}
      {...props}
    />
  );
}

export { List, ListDivider, ListItem };