SEED Design

List

List 컴포넌트는 정보를 구조화된 목록 형태로 표시하는 데 사용됩니다.

import { List, ListDivider, ListItem } from "@/registry/ui/list";
import {
  IconILowercaseSerifCircleLine,
  IconPersonCircleLine,
} from "@karrotmarket/react-monochrome-icon";
import { Icon } from "@seed-design/react";

export default function ListPreview() {
  return (
    <List width="full">
      <ListItem title="기본 리스트 아이템" />
      <ListDivider />
      <ListItem
        prefix={<Icon svg={<IconPersonCircleLine />} />}
        title="아이콘이 있는 리스트 아이템"
        detail="부가 정보가 포함된 설명"
        suffix={<Icon svg={<IconILowercaseSerifCircleLine />} />}
      />
    </List>
  );
}

Installation

npx @seed-design/cli@latest add list

Props

List

VStackProps와 동일합니다.

ListItem

PropTypeDefault
title?
ReactNode
-
detail?
ReactNode
-
prefix?
ReactNode
-
suffix?
ReactNode
-
alignItems?
"flex-start" | "flex-end" | "center" | "stretch" | "flexStart" | "flexEnd"
-
highlighted?
boolean
false

ListButtonItem

PropTypeDefault
title?
ReactNode
-
detail?
ReactNode
-
prefix?
ReactNode
-
suffix?
ReactNode
-
rootRef?
Ref<HTMLLIElement>
-
alignItems?
"flex-start" | "flex-end" | "center" | "stretch" | "flexStart" | "flexEnd"
-
highlighted?
boolean
false

ListLinkItem

PropTypeDefault
title?
ReactNode
-
detail?
ReactNode
-
prefix?
ReactNode
-
suffix?
ReactNode
-
rootRef?
Ref<HTMLLIElement>
-
alignItems?
"flex-start" | "flex-end" | "center" | "stretch" | "flexStart" | "flexEnd"
-
highlighted?
boolean
false

ListCheckItem

PropTypeDefault
title?
ReactNode
-
detail?
ReactNode
-
prefix?
ReactNode
-
suffix?
ReactNode
-
inputProps?
InputHTMLAttributes<HTMLInputElement>
-
rootRef?
Ref<HTMLLabelElement>
-
defaultChecked?
boolean
-
alignItems?
"flex-start" | "flex-end" | "center" | "stretch" | "flexStart" | "flexEnd"
-
highlighted?
boolean
false
disabled?
boolean
-
invalid?
boolean
-
required?
boolean
-
checked?
boolean
-
onCheckedChange?
((checked: boolean) => void)
-
indeterminate?
boolean
-

ListRadioItem

PropTypeDefault
title?
ReactNode
-
detail?
ReactNode
-
prefix?
ReactNode
-
suffix?
ReactNode
-
inputProps?
InputHTMLAttributes<HTMLInputElement>
-
rootRef?
Ref<HTMLLabelElement>
-
alignItems?
"flex-start" | "flex-end" | "center" | "stretch" | "flexStart" | "flexEnd"
-
highlighted?
boolean
false
value?
string
-
disabled?
boolean
-
invalid?
boolean
-

ListDivider

DividerProps와 동일하나, 기본적으로 <li aria-hidden></li>로 렌더링됩니다. Listfieldset 등으로 바꿔 사용하는 경우 ListDivider 역시 적절한 태그로 교체해주세요.

PropTypeDefault
as?
"hr" | "div" | "li"
"li"
aria-hidden?
Booleanish
true
color?
ScopedColorStroke | ScopedColorPalette | (string & {})
"stroke.neutralMuted"
thickness?
0 | (string & {}) | 1
1
orientation?
"horizontal" | "vertical"
"horizontal"

Examples

리스트 항목에 Prefix 또는 Suffix 추가하기

"use client";

import {
  IconArrowUpBracketDownLine,
  IconILowercaseSerifCircleLine,
} from "@karrotmarket/react-monochrome-icon";
import { Icon } from "@seed-design/react";
import { useState } from "react";
import { ActionButton } from "seed-design/ui/action-button";
import { Avatar } from "seed-design/ui/avatar";
import { IdentityPlaceholder } from "seed-design/ui/identity-placeholder";
import { List, ListDivider, ListItem } from "seed-design/ui/list";
import { ToggleButton } from "seed-design/ui/toggle-button";

export default function ListAffixes() {
  const [isToggleButtonPressed, setIsToggleButtonPressed] = useState(false);

  return (
    <List width="full" py="x4">
      <ListItem
        prefix={
          <Avatar
            size="48"
            src="https://avatars.githubusercontent.com/u/54893898?v=4"
            fallback={<IdentityPlaceholder />}
          />
        }
        title="Prefix에 Avatar 넣기"
        detail="Amet elit ullamco magna."
      />
      <ListDivider />
      <ListItem
        prefix={<Avatar size="48" fallback={<IdentityPlaceholder />} />}
        title="Prefix에 Avatar 넣고 상단으로 정렬하기. 일반적으로 `title`이 길어질 때 사용합니다. Veniam elit velit esse ea incididunt sunt sit aute."
        detail="Et proident sit ullamco ut voluptate."
        alignItems="flex-start"
      />
      <ListDivider />
      <ListItem
        title="Prefix에 아이콘 넣기"
        detail="Deserunt nulla elit est."
        prefix={<Icon svg={<IconILowercaseSerifCircleLine />} />}
      />
      <ListDivider />
      <ListItem
        title="Suffix에 Action Button 넣기"
        detail="Veniam non est non ut consequat."
        suffix={<ActionButton size="xsmall">액션 버튼</ActionButton>}
      />
      <ListDivider />
      <ListItem
        title="Suffix에 Action Button (Ghost) 넣기"
        detail="Deserunt nulla elit est."
        suffix={
          <ActionButton size="small" variant="ghost" layout="iconOnly" aria-label="공유">
            <Icon svg={<IconArrowUpBracketDownLine />} />
          </ActionButton>
        }
      />
      <ListDivider />
      <ListItem
        title="Suffix에 Toggle Button 넣기"
        detail="Sit eu incididunt aute ea elit ex."
        suffix={
          <ToggleButton
            size="xsmall"
            pressed={isToggleButtonPressed}
            onPressedChange={setIsToggleButtonPressed}
          >
            {isToggleButtonPressed ? "선택됨" : "토글 버튼"}
          </ToggleButton>
        }
      />
    </List>
  );
}

리스트 항목 전체를 클릭 가능하게 만들기

ListButtonItem 또는 ListLinkItem를 사용해서 리스트 항목 전체를 클릭 가능하도록 만들 수 있습니다.

"use client";

import {
  IconArrowUpRightLine,
  IconCheckmarkFill,
  IconChevronRightLine,
  IconPlusFill,
  IconSquare2StackedFill,
} from "@karrotmarket/react-monochrome-icon";
import { PrefixIcon, Icon } from "@seed-design/react";
import { useCallback, useState } from "react";
import { List, ListDivider, ListItem, ListButtonItem, ListLinkItem } from "seed-design/ui/list";
import { ActionButton } from "seed-design/ui/action-button";
import { ToggleButton } from "seed-design/ui/toggle-button";

const href = "https://www.daangn.com";

export default function ListClickable() {
  const [isSubscribed, setIsSubscribed] = useState(false);
  const [isCopied, setIsCopied] = useState(false);

  const onCopyClick = useCallback(() => {
    navigator.clipboard.writeText(href);
    setIsCopied(true);

    setTimeout(() => setIsCopied(false), 2000);
  }, []);

  return (
    <List width="full">
      <ListItem
        title="ListItem은 클릭할 수 없어요. 눌러보세요."
        detail="우측의 Action Button만 클릭할 수 있어요"
        suffix={
          <ActionButton size="xsmall" onClick={() => alert("편집 클릭됨")}>
            편집
          </ActionButton>
        }
      />
      <ListDivider />
      <ListButtonItem
        title="ListButtonItem은 클릭할 수 있어요. 눌러보세요."
        detail="리스트 항목 전체와 우측의 Toggle Button 각각을 클릭할 수 있어요"
        onClick={() => alert("리스트 아이템 클릭됨")}
        suffix={
          <>
            <ToggleButton size="xsmall" pressed={isSubscribed} onPressedChange={setIsSubscribed}>
              <PrefixIcon svg={isSubscribed ? <IconCheckmarkFill /> : <IconPlusFill />} />
              {isSubscribed ? "모아보는 중" : "모아보기"}
            </ToggleButton>
            <Icon svg={<IconChevronRightLine />} />
          </>
        }
      />
      <ListDivider />
      <ListLinkItem
        title="ListLinkItem도 클릭할 수 있어요. 눌러보세요."
        detail="리스트 항목 전체와 우측의 Action Button 각각을 클릭할 수 있어요"
        suffix={
          <>
            <ActionButton size="xsmall" onClick={onCopyClick}>
              <PrefixIcon svg={isCopied ? <IconCheckmarkFill /> : <IconSquare2StackedFill />} />
              {isCopied ? "복사됨" : "URL 복사"}
            </ActionButton>
            <Icon svg={<IconArrowUpRightLine />} />
          </>
        }
        href={href}
        target="_blank"
        rel="noreferrer"
      />
    </List>
  );
}

리스트 항목 전체를 폼 요소로 만들기

ListCheckItemListRadioItem를 사용해서 리스트 항목 전체를 폼 요소로 만들 수 있습니다. Checkmark 또는 RadioMark와 같은 컨트롤 요소를 prefixsuffix 영역에 넣어 사용합니다.

import { Badge, HStack } from "@seed-design/react";
import { List, ListDivider, ListCheckItem } from "seed-design/ui/list";
import { Checkmark } from "seed-design/ui/checkbox";

export default function ListCheckbox() {
  return (
    <List as="fieldset" width="full">
      <ListCheckItem
        title={
          <HStack gap="x1_5">
            <span>알림 수신 동의</span>
            <Badge variant="weak">권장</Badge>
          </HStack>
        }
        detail="푸시 알림을 받으시겠습니까?"
        suffix={<Checkmark size="large" />}
        defaultChecked
      />
      <ListDivider as="div" />
      <ListCheckItem
        prefix={<Checkmark size="large" />}
        title="마케팅 정보 수신 동의"
        detail="마케팅 정보를 받으시겠습니까?"
        defaultChecked
      />
      <ListDivider as="div" />
      <ListCheckItem prefix={<Checkmark size="large" variant="ghost" />} title="Ghost Variant" />
    </List>
  );
}
import { RadioGroup } from "@seed-design/react";
import { List, ListDivider, ListRadioItem } from "seed-design/ui/list";
import { RadioMark } from "seed-design/ui/radio-group";

export default function ListRadio() {
  return (
    <List width="full" asChild>
      <RadioGroup.Root defaultValue="option1" aria-label="옵션 선택">
        <ListRadioItem
          value="option1"
          title="옵션 1"
          detail="첫 번째 선택지"
          suffix={<RadioMark size="large" />}
        />
        <ListDivider as="div" />
        <ListRadioItem
          prefix={<RadioMark size="large" />}
          value="option2"
          title="옵션 2"
          detail="두 번째 선택지"
        />
        <ListDivider as="div" />
        <ListRadioItem
          prefix={<RadioMark size="large" />}
          value="option3"
          title="옵션 3"
          detail="세 번째 선택지"
        />
      </RadioGroup.Root>
    </List>
  );
}

접근 가능하게 만들기

List는 기본적으로 <ul>입니다. ListCheckItemListRadioItem를 사용하는 경우 List에 적절한 role을 부여해야 합니다.

<List as="fieldset">
  <ListCheckItem
    suffix={<Checkmark size="large" />}
    title="알림 수신 동의"
    detail="푸시 알림을 받으시겠습니까?"
  />
  <ListCheckItem
    suffix={<Checkmark size="large" />}
    title="마케팅 정보 수신 동의"
    detail="마케팅 정보를 받으시겠습니까?"
  />
</List>
<List asChild>
  <RadioGroup.Root defaultValue="짜장" aria-label="점심 메뉴"> {/* <div role="radiogroup"> */}
    <ListRadioItem
      suffix={<RadioMark size="large" />}
      value="짜장"
      title="짜장"
    />
    <ListRadioItem
      suffix={<RadioMark size="large" />}
      value="짬뽕"
      title="짬뽕"
    />
  </RadioGroup.Root>
</List>

Disabled

import {
  IconChevronRightLine,
  IconPersonCircleLine,
  IconSlashCircleLine,
} from "@karrotmarket/react-monochrome-icon";
import { Divider, Icon, RadioGroup, VStack } from "@seed-design/react";
import { List, ListButtonItem, ListCheckItem, ListRadioItem } from "seed-design/ui/list";
import { Checkmark } from "seed-design/ui/checkbox";
import { RadioMark } from "seed-design/ui/radio-group";

export default function ListDisabled() {
  return (
    <VStack width="full">
      <List>
        <ListButtonItem
          prefix={<Icon svg={<IconPersonCircleLine />} />}
          title="활성화된 ListButtonItem"
          suffix={<Icon svg={<IconChevronRightLine />} />}
        />
      </List>
      <List as="fieldset">
        <ListCheckItem
          prefix={<Icon svg={<IconPersonCircleLine />} />}
          title="활성화된 ListCheckItem"
          suffix={<Checkmark size="large" />}
        />
      </List>
      <List asChild>
        <RadioGroup.Root defaultValue="foo" aria-label="옵션 선택">
          <ListRadioItem
            prefix={<Icon svg={<IconPersonCircleLine />} />}
            title="활성화된 ListRadioItem"
            suffix={<RadioMark size="large" />}
            value="foo"
          />
        </RadioGroup.Root>
      </List>
      <Divider />
      <List>
        <ListButtonItem
          disabled
          prefix={<Icon svg={<IconSlashCircleLine />} />}
          title="비활성화된 ListButtonItem"
          suffix={<Icon svg={<IconChevronRightLine />} />}
        />
      </List>
      <List as="fieldset">
        <ListCheckItem
          disabled
          prefix={<Icon svg={<IconSlashCircleLine />} />}
          title="비활성화된 ListCheckItem"
          suffix={<Checkmark size="large" />}
        />
      </List>
      <List asChild>
        <RadioGroup.Root defaultValue="foo" aria-label="옵션 선택">
          <ListRadioItem
            disabled
            prefix={<Icon svg={<IconSlashCircleLine />} />}
            title="비활성화된 ListRadioItem"
            suffix={<RadioMark size="large" />}
            value="foo"
          />
        </RadioGroup.Root>
      </List>
    </VStack>
  );
}

Variants

Highlighted

"use client";

import { IconPersonCircleLine } from "@karrotmarket/react-monochrome-icon";
import { Box, Icon, VStack } from "@seed-design/react";
import { useState } from "react";
import { List, ListDivider, ListItem } from "seed-design/ui/list";
import { Switch } from "seed-design/ui/switch";

export default function ListHighlighted() {
  const [highlighted, setHighlighted] = useState(true);

  return (
    <VStack width="full" gap="x4">
      <List>
        <ListItem
          prefix={<Icon svg={<IconPersonCircleLine />} />}
          title="하이라이트되지 않은 항목"
          detail="Enim aute duis magna mollit aute sit aliquip duis ut tempor sunt."
        />
        <ListDivider />
        <ListItem
          highlighted
          prefix={<Icon svg={<IconPersonCircleLine />} />}
          title="하이라이트된 항목"
          detail="Enim aute duis magna mollit aute sit aliquip duis ut tempor sunt."
        />
      </List>
      <List>
        <ListItem
          prefix={<Icon svg={<IconPersonCircleLine />} />}
          title="하이라이트"
          highlighted={highlighted}
        />
      </List>
      <Box alignSelf="center">
        <Switch
          size="24"
          label="highlight"
          checked={highlighted}
          onCheckedChange={setHighlighted}
        />
      </Box>
    </VStack>
  );
}

Snippet 커스텀 가이드

구조와 영역별 역할

@seed-design/react 패키지에서 제공하는 List 컴포넌트는 다음과 같은 구조로 구성됩니다.

List.Root
└── List.Item
    ├── List.Prefix (선택사항)
    ├── List.Content
    │   ├── List.Title
    │   └── List.Detail (선택사항)
    └── List.Suffix (선택사항)
import { List, Icon } from "@seed-design/react";

<List.Root>
  <List.Item>
    <List.Prefix>
      <Icon svg={<IconPersonCircleLine />} />
    </List.Prefix>
    <List.Content>
      <List.Title>내 프로필</List.Title>
      <List.Detail>다른 사람들에게 보이는 내 정보를 관리합니다.</List.Detail>
    </List.Content>
    <List.Suffix>
      <Icon svg={<IconArrowRightLine />} />
    </List.Suffix>
  </List.Item>
  <List.Item>
    {/* ... */}
  </List.Item>
</List.Root>
  • List.Root: 모든 리스트 항목을 감싸는 컨테이너 역할
    • List.Item: 개별 리스트 항목. 클릭 가능한 영역을 정의
    • List.Prefix: 아이콘, Avatar, Checkmark 등을 표시할 수 있는 시작 영역
    • List.Content: 주요 콘텐츠가 들어가는 중앙 영역
      • List.Title: 리스트 항목의 제목
      • List.Detail: 부가 설명이나 세부 정보
    • List.Suffix: 아이콘, Action Button, Toggle Button 등을 표시할 수 있는 끝 영역

asChild prop으로 적절한 시맨틱 요소와 조합하기

Composition

asChild prop에 대해 자세히 알아봅니다.

List.ContentasChild prop을 사용하는 경우

리스트 항목 전체 영역을 클릭 가능한 버튼으로 만드는 경우 활용할 수 있는 패턴입니다. 이 경우 List.ItemasChild prop을 사용하지 않도록 유의하세요. List.Prefix 또는 List.Suffix에 버튼을 넣는 경우 button이 중첩되는 등 유효하지 않은 HTML이 생성됩니다.

import { List as SeedList } from "@seed-design/react";

<SeedList.Item>
  <SeedList.Content asChild>
    <button type="button" onClick={() => alert("사용자 클릭됨")}>
      <SeedList.Title>사용자</SeedList.Title>
    </button>
  </SeedList.Content>
  <SeedList.Suffix>
    <ActionButton
      size="xsmall"
      variant="brandSolid"
      onClick={() => alert("보기 클릭됨")}
    >
      보기
    </ActionButton>
  </SeedList.Suffix>
</SeedList.Item>

Snippet으로 제공되는 ListButtonItemListLinkItem는 이 패턴을 쉽게 구현할 수 있도록 돕습니다.

import { ListButtonItem } from "seed-design/ui/list";

<ListButtonItem
  onClick={() => alert("사용자 클릭됨")}
  title="사용자"
  detail="항목 6개"
  suffix={
    <ActionButton
      size="xsmall"
      variant="brandSolid"
      onClick={() => alert("보기 클릭됨")}
    >
      보기
    </ActionButton>
  }
/>

List.ItemasChild prop을 사용하는 경우

리스트 항목 전체 영역을 label로 만들고, List.Prefix 또는 List.SuffixCheckmark 또는 RadioMark를 넣는 경우 활용할 수 있는 패턴입니다.

import { List as SeedList } from "@seed-design/react";
import { Checkbox } from "@seed-design/react/primitive";

<SeedList.Item asChild> {/* <label> */}
  <Checkbox.Root defaultChecked>
    <SeedList.Content>
      <SeedList.Title>동의</SeedList.Title>
    </SeedList.Content>
    <SeedList.Suffix>
      <Checkmark />
    </SeedList.Suffix>
    <Checkbox.HiddenInput />
  </Checkbox.Root>
</SeedList.Item>

Snippet으로 제공되는 ListCheckItemListRadioItem는 이 패턴을 쉽게 구현할 수 있도록 돕습니다.

import { ListCheckItem } from "seed-design/ui/list";

<ListCheckItem
  defaultChecked
  title="동의"
  suffix={<Checkmark size="large" />}
/>

Last updated on