Show · List

List

Lists display a set of related contents

Demos

How to import

import { Avatar, Button, ButtonGroup, Checkbox, CheckboxGroup, Descriptions, Input, List, Pagination, Radio, RadioGroup, Rating, Skeleton, Spin } from '@fibrous/semi-ui-solid';

Basic Usage

You can use size to size list. Supported values include large, default, small. Header and Footer customized.

Default Size

Header
  • 从明天起,做一个幸福的人
  • 喂马,劈柴,周游世界
  • 从明天起,关心粮食和蔬菜
  • 我有一所房子,面朝大海,春暖花开

Small Size

Header
  • 从明天起,做一个幸福的人
  • 喂马,劈柴,周游世界
  • 从明天起,关心粮食和蔬菜
  • 我有一所房子,面朝大海,春暖花开

Large Size

Header
  • 从明天起,做一个幸福的人
  • 喂马,劈柴,周游世界
  • 从明天起,关心粮食和蔬菜
  • 我有一所房子,面朝大海,春暖花开
Code
import { List } from '@fibrous/semi-ui-solid';

const data = ['从明天起,做一个幸福的人', '喂马,劈柴,周游世界', '从明天起,关心粮食和蔬菜', '我有一所房子,面朝大海,春暖花开'];

export default function ListDemo() {
    return (
        <div>
            <div style={{ 'margin-right': '16px' }}>
                <h3 style={{ 'margin-bottom': '16px' }}>Default Size</h3>
                <List header={<div>Header</div>} footer={<div>Footer</div>} bordered dataSource={data} renderItem={(item) => <List.Item>{item}</List.Item>} />
            </div>
            <div style={{ 'margin-right': '16px' }}>
                <h3 style={{ margin: '16px 0' }}>Small Size</h3>
                <List size="small" header={<div>Header</div>} footer={<div>Footer</div>} bordered dataSource={data} renderItem={(item) => <List.Item>{item}</List.Item>} />
            </div>
            <div style={{ 'margin-right': '16px' }}>
                <h3 style={{ margin: '16px 0' }}>Large Size</h3>
                <List size="large" header={<div>Header</div>} footer={<div>Footer</div>} bordered dataSource={data} renderItem={(item) => <List.Item>{item}</List.Item>} />
            </div>
        </div>
    );
}

Template

List.Item has a built-in template consisting of: header, main, and extra. The alignment of header and main set by align properties using one of flex-start(default), flex-end, center, baseline, and stretch .

  • SE
    示例标题

    Semi Design 是由抖音前端团队与 UED 团队共同设计开发并维护的设计系统。设计系统包含设计语言以及一整套可复用的前端组件,帮助设计师与开发者更容易地打造高质量的、用户体验一致的、符合设计规范的 Web 应用。

  • SE
    示例标题

    Semi Design 是由抖音前端团队与 UED 团队共同设计开发并维护的设计系统。设计系统包含设计语言以及一整套可复用的前端组件,帮助设计师与开发者更容易地打造高质量的、用户体验一致的、符合设计规范的 Web 应用。

  • SE
    示例标题

    Semi Design 以用户中心、内容优先、设计人性化的设计系统,打造一致、好看、好用、高效的用户体验。

Code
import type { JSX } from 'solid-js';
import { Avatar, Button, ButtonGroup, List } from '@fibrous/semi-ui-solid';

// Each text carries its own style; a JSX element is a DOM node that can only sit in one place, so the <p> is built per row
const data: { text: string; style: JSX.CSSProperties }[] = [
    {
        text: 'Semi Design 是由抖音前端团队与 UED 团队共同设计开发并维护的设计系统。设计系统包含设计语言以及一整套可复用的前端组件,帮助设计师与开发者更容易地打造高质量的、用户体验一致的、符合设计规范的 Web 应用。',
        style: { color: 'var(--semi-color-text-2)', margin: '4px 0', width: '420px', 'white-space': 'nowrap', overflow: 'hidden', 'text-overflow': 'ellipsis' },
    },
    {
        text: 'Semi Design 是由抖音前端团队与 UED 团队共同设计开发并维护的设计系统。设计系统包含设计语言以及一整套可复用的前端组件,帮助设计师与开发者更容易地打造高质量的、用户体验一致的、符合设计规范的 Web 应用。',
        style: { color: 'var(--semi-color-text-2)', margin: '4px 0', width: '500px' },
    },
    {
        text: 'Semi Design 以用户中心、内容优先、设计人性化的设计系统,打造一致、好看、好用、高效的用户体验。',
        style: { color: 'var(--semi-color-text-2)', margin: '4px 0', width: '500px' },
    },
];

export default function ListDemo() {
    return (
        <div style={{ padding: '12px', border: '1px solid var(--semi-color-border)', margin: '12px' }}>
            <List
                dataSource={data}
                renderItem={(item) => (
                    <List.Item
                        header={<Avatar color="blue">SE</Avatar>}
                        main={
                            <div>
                                <span style={{ color: 'var(--semi-color-text-0)', 'font-weight': '500' }}>示例标题</span>
                                <p style={item.style}>{item.text}</p>
                            </div>
                        }
                        extra={
                            <ButtonGroup theme="borderless">
                                <Button>编辑</Button>
                                <Button>更多</Button>
                            </ButtonGroup>
                        }
                    />
                )}
            />
        </div>
    );
}

Layout

Use layout property to set list layout, one of vertical(default) or horizontal.

  • SE
    Semi Design Title 1

    Semi Design 设计系统包含设计语言以及一整套可复用的前端组件,帮助设计师与开发者更容易地打造高质量的、用户体验一致的、符合设计规范的 Web 应用。

  • SE
    Semi Design Title 2

    Semi Design 设计系统包含设计语言以及一整套可复用的前端组件,帮助设计师与开发者更容易地打造高质量的、用户体验一致的、符合设计规范的 Web 应用。

  • SE
    Semi Design Title 3

    Semi Design 设计系统包含设计语言以及一整套可复用的前端组件,帮助设计师与开发者更容易地打造高质量的、用户体验一致的、符合设计规范的 Web 应用。

Code
import { Avatar, List, type AvatarColor } from '@fibrous/semi-ui-solid';

interface Item {
    title: string;
    color: AvatarColor;
}

const data: Item[] = [
    {
        title: 'Semi Design Title 1',
        color: 'light-blue',
    },
    {
        title: 'Semi Design Title 2',
        color: 'grey',
    },
    {
        title: 'Semi Design Title 3',
        color: 'light-green',
    },
];

export default function ListDemo() {
    return (
        <div style={{ padding: '12px', border: '1px solid var(--semi-color-border)', margin: '12px' }}>
            <List
                dataSource={data}
                layout="horizontal"
                renderItem={(item) => (
                    <List.Item
                        header={<Avatar color={item.color}>SE</Avatar>}
                        main={
                            <div>
                                <span style={{ color: 'var(--semi-color-text-0)', 'font-weight': '500' }}>{item.title}</span>
                                <p style={{ color: 'var(--semi-color-text-2)', margin: '4px 0' }}>Semi Design 设计系统包含设计语言以及一整套可复用的前端组件,帮助设计师与开发者更容易地打造高质量的、用户体验一致的、符合设计规范的 Web 应用。</p>
                            </div>
                        }
                    />
                )}
            />
        </div>
    );
}

Grid

Use grid property to set grid layout. Use span to set the number of occupying spaces for each item and gutter for spacing between items.

  • Semi UI

    满意度
    反馈数124
  • Semi DSM

    满意度
    反馈数108
  • Semi D2C

    满意度
    反馈数244
  • Code
    import { Button, ButtonGroup, Descriptions, type DescriptionsData, List, Rating } from '@fibrous/semi-ui-solid';
    
    const data = [
        {
            title: 'Semi UI',
            rating: 4.5,
            feedbacks: 124,
        },
        {
            title: 'Semi DSM',
            rating: 4,
            feedbacks: 108,
        },
        {
            title: 'Semi D2C',
            rating: 4.5,
            feedbacks: 244,
        },
    ];
    
    const style = {
        border: '1px solid var(--semi-color-border)',
        'background-color': 'var(--semi-color-bg-2)',
        'border-radius': '3px',
        'padding-left': '20px',
    };
    
    const renderStat = (stat: DescriptionsData, index: number) => (index === 0 ? <Rating allowHalf size="small" value={typeof stat.value === 'number' ? stat.value : undefined} /> : stat.value);
    
    export default function ListDemo() {
        return (
            <div>
                <List
                    grid={{
                        gutter: 12,
                        span: 6,
                    }}
                    dataSource={data}
                    renderItem={(item) => (
                        <List.Item style={style}>
                            <div>
                                <h3 style={{ color: 'var(--semi-color-text-0)', 'font-weight': '500' }}>{item.title}</h3>
                                <Descriptions
                                    align="center"
                                    size="small"
                                    row
                                    data={[
                                        { key: '满意度', value: item.rating },
                                        { key: '反馈数', value: item.feedbacks },
                                    ]}
                                    renderValue={renderStat}
                                />
                                <div style={{ margin: '12px 0', display: 'flex', 'justify-content': 'flex-end' }}>
                                    <ButtonGroup theme="borderless" style={{ 'margin-top': '8px' }}>
                                        <Button>编辑</Button>
                                        <Button>更多</Button>
                                    </ButtonGroup>
                                </div>
                            </div>
                        </List.Item>
                    )}
                />
            </div>
        );
    }

    Responsive List

    Refer to Grid for responsive dimensions.

  • 审核管理平台

    满意度
    反馈数124
  • 扁鹊

    满意度
    反馈数108
  • 直播审核平台

    满意度
    反馈数244
  • 抖音安全测试

    满意度
    反馈数189
  • 内容平台

    满意度
    反馈数128
  • 策略平台

    满意度
    反馈数156
  • Code
    import { Button, ButtonGroup, Descriptions, type DescriptionsData, List, Rating } from '@fibrous/semi-ui-solid';
    
    const data = [
        {
            title: '审核管理平台',
            rating: 4.5,
            feedbacks: 124,
        },
        {
            title: '扁鹊',
            rating: 4,
            feedbacks: 108,
        },
        {
            title: '直播审核平台',
            rating: 3.5,
            feedbacks: 244,
        },
        {
            title: '抖音安全测试',
            feedbacks: 189,
        },
        {
            title: '内容平台',
            rating: 3,
            feedbacks: 128,
        },
        {
            title: '策略平台',
            rating: 4,
            feedbacks: 156,
        },
    ];
    
    const style = {
        border: '1px solid var(--semi-color-border)',
        'background-color': 'var(--semi-color-bg-2)',
        'border-radius': '3px',
        'padding-left': '20px',
        margin: '8px 2px',
    };
    
    const renderStat = (stat: DescriptionsData, index: number) => (index === 0 ? <Rating allowHalf size="small" value={typeof stat.value === 'number' ? stat.value : undefined} /> : stat.value);
    
    export default function ListDemo() {
        return (
            <div>
                <List
                    grid={{
                        gutter: 12,
                        xs: 0,
                        sm: 0,
                        md: 12,
                        lg: 8,
                        xl: 8,
                        xxl: 6,
                    }}
                    dataSource={data}
                    renderItem={(item) => (
                        <List.Item style={style}>
                            <div>
                                <h3 style={{ color: 'var(--semi-color-text-0)', 'font-weight': '500' }}>{item.title}</h3>
                                <Descriptions
                                    align="center"
                                    size="small"
                                    row
                                    data={[
                                        { key: '满意度', value: item.rating },
                                        { key: '反馈数', value: item.feedbacks },
                                    ]}
                                    renderValue={renderStat}
                                />
                                <div style={{ margin: '12px 0', display: 'flex', 'justify-content': 'flex-end' }}>
                                    <ButtonGroup theme="borderless" style={{ 'margin-top': '8px' }}>
                                        <Button>编辑</Button>
                                        <Button>更多</Button>
                                    </ButtonGroup>
                                </div>
                            </div>
                        </List.Item>
                    )}
                />
            </div>
        );
    }

    Load More

    You can use loadMore to achieve loading state for more incoming contents.

    Code
    import { createMemo, createSignal, onCleanup, onMount, Show } from 'solid-js';
    import { Avatar, Button, List, Skeleton, type AvatarColor } from '@fibrous/semi-ui-solid';
    
    interface Book {
        color?: AvatarColor;
        title?: string;
        loading: boolean;
    }
    
    const count = 3;
    const data: Book[] = [];
    for (let i = 0; i < 40; i++) {
        data.push({
            color: 'grey',
            title: `Semi Design Title ${i}`,
            loading: false,
        });
    }
    const placeholders: Book[] = [{ loading: true }, { loading: true }, { loading: true }];
    
    const fetchData = (page: number) => {
        return new Promise<Book[]>((res) => {
            setTimeout(() => {
                res(data.slice(page * count, page * count + count));
            }, 1000);
        });
    };
    
    function Placeholder() {
        return (
            <div
                style={{
                    display: 'flex',
                    'align-items': 'flex-start',
                    padding: '12px',
                    'border-bottom': '1px solid var(--semi-color-border)',
                }}
            >
                <Skeleton.Avatar style={{ 'margin-right': '12px' }} />
                <div>
                    <Skeleton.Title style={{ width: '120px', 'margin-bottom': '12px', 'margin-top': '12px' }} />
                    <Skeleton.Paragraph style={{ width: '600px' }} rows={2} />
                </div>
            </div>
        );
    }
    
    export default function ListDemo() {
        let page = 0;
        const [loading, setLoading] = createSignal(true);
        const [dataSource, setDataSource] = createSignal<Book[]>([]);
        const [noMore, setNoMore] = createSignal(false);
        const list = createMemo(() => (loading() ? [...dataSource(), ...placeholders] : dataSource()));
    
        let disposed = false;
        onCleanup(() => {
            disposed = true;
        });
    
        const load = (index: number) => {
            void fetchData(index).then((newDataSource) => {
                if (disposed) return;
                setDataSource((prevData) => [...prevData, ...newDataSource]);
                setLoading(false);
                setNoMore(!newDataSource.length);
            });
        };
    
        onMount(() => {
            load(0);
        });
    
        const onLoadMore = () => {
            setLoading(true);
            load(++page);
        };
    
        return (
            <List
                loading={loading()}
                loadMore={
                    <Show when={!loading() && !noMore()}>
                        <div
                            style={{
                                'text-align': 'center',
                                'margin-top': '12px',
                                height: '32px',
                                'line-height': '32px',
                            }}
                        >
                            <Button onClick={onLoadMore}>显示更多</Button>
                        </div>
                    </Show>
                }
                dataSource={list()}
                renderItem={(item) => (
                    <Skeleton placeholder={<Placeholder />} loading={item.loading}>
                        <List.Item
                            header={<Avatar color={item.color}>SE</Avatar>}
                            main={
                                <div>
                                    <span style={{ color: 'var(--semi-color-text-0)', 'font-weight': '500' }}>{item.title}</span>
                                    <p style={{ color: 'var(--semi-color-text-2)', margin: '4px 0' }}>Semi Design 设计系统包含设计语言以及一整套可复用的前端组件,帮助设计师与开发者更容易地打造高质量的、用户体验一致的、符合设计规范的 Web 应用。</p>
                                </div>
                            }
                        />
                    </Skeleton>
                )}
            />
        );
    }

    Scroll to Load

    You can listen to the scroll event of the scroll container and load the next page near the bottom. Recommended interaction could be reveal a loadmore button after three scrolling loads.

    暂无数据
    Code
    import { createSignal, onCleanup, onMount, Show } from 'solid-js';
    import { Avatar, Button, List, Spin } from '@fibrous/semi-ui-solid';
    
    interface Book {
        title: string;
    }
    
    const count = 5;
    const dataList: Book[] = Array.from({ length: 100 }, (_, i) => ({ title: `Semi Design Title ${i}` }));
    
    const fetchData = (page: number) =>
        new Promise<Book[]>((res) => {
            setTimeout(() => {
                res(dataList.slice(page * count, page * count + count));
            }, 1000);
        });
    
    export default function ListDemo() {
        const [page, setPage] = createSignal(0);
        const [loading, setLoading] = createSignal(true);
        const [dataSource, setDataSource] = createSignal<Book[]>([]);
        const [hasMore, setHasMore] = createSignal(true);
    
        let disposed = false;
        onCleanup(() => {
            disposed = true;
        });
    
        const showLoadMore = () => (page() + 1) % 4 === 0;
    
        const load = (index: number) => {
            setLoading(true);
            void fetchData(index).then((newDataSource) => {
                if (disposed) return;
                setDataSource((prevData) => [...prevData, ...newDataSource]);
                setLoading(false);
                setHasMore(!!newDataSource.length);
            });
        };
    
        onMount(() => {
            load(0);
        });
    
        const onLoadMore = () => {
            const next = page() + 1;
            setPage(next);
            load(next);
        };
    
        // Near the bottom of the scroll container, load the next page unless the load-more button takes over
        const onScroll = (event: { currentTarget: HTMLElement }) => {
            const el = event.currentTarget;
            if (loading() || !hasMore() || showLoadMore()) return;
            if (el.scrollHeight - el.scrollTop - el.clientHeight <= 20) onLoadMore();
        };
    
        return (
            <section aria-label="可滚动列表" class="light-scrollbar" tabIndex={0} style={{ height: '420px', overflow: 'auto', border: '1px solid var(--semi-color-border)', padding: '10px' }} onScroll={onScroll}>
                <List
                    loadMore={
                        <Show when={!loading() && hasMore() && showLoadMore()}>
                            <div style={{ 'text-align': 'center', 'margin-top': '12px', height: '32px', 'line-height': '32px' }}>
                                <Button onClick={onLoadMore}>显示更多</Button>
                            </div>
                        </Show>
                    }
                    dataSource={dataSource()}
                    renderItem={(item) => (
                        <List.Item
                            header={<Avatar color="grey">SE</Avatar>}
                            main={
                                <div>
                                    <span style={{ color: 'var(--semi-color-text-0)', 'font-weight': '500' }}>{item.title}</span>
                                    <p style={{ color: 'var(--semi-color-text-2)', margin: '4px 0' }}>Semi Design 设计系统包含设计语言以及一整套可复用的前端组件,帮助设计师与开发者更容易地打造高质量的、用户体验一致的、符合设计规范的 Web 应用。</p>
                                </div>
                            }
                        />
                    )}
                />
                <Show when={loading() && hasMore()}>
                    <div style={{ 'text-align': 'center' }}>
                        <Spin />
                    </div>
                </Show>
            </section>
        );
    }

    Scroll to Load Infinite Lists

    You can integrate TanStack Virtual to implement infinite scrolling lists with virtualization, which renders only the visible rows and improves the performance for large amounts of data.

    Code
    import { createEffect, For } from 'solid-js';
    import { createStore } from 'solid-js/store';
    import { createVirtualizer } from '@tanstack/solid-virtual';
    import { Avatar, List, Spin } from '@fibrous/semi-ui-solid';
    
    interface Book {
        title: string;
    }
    
    const rowCount = 50;
    const rowHeight = 118;
    // Rows fetched ahead of the window so they arrive before they scroll into view
    const prefetch = 15;
    const dataList: Book[] = Array.from({ length: rowCount }, (_, i) => ({ title: `Semi Design Title ${i}` }));
    
    export default function ListDemo() {
        let scroller: HTMLElement | undefined;
        const [loaded, setLoaded] = createStore<Record<number, boolean>>({});
        // A requested index is never fetched again, even while pending
        const requested = new Set<number>();
        const virtualizer = createVirtualizer({ count: rowCount, getScrollElement: () => scroller ?? null, estimateSize: () => rowHeight, overscan: 2 });
    
        createEffect(() => {
            const items = virtualizer.getVirtualItems();
            if (!items.length) return;
            const wanted: number[] = [];
            for (let i = items[0].index; i <= Math.min(items[items.length - 1].index + prefetch, rowCount - 1); i++) {
                if (!requested.has(i)) {
                    requested.add(i);
                    wanted.push(i);
                }
            }
            if (!wanted.length) return;
            setTimeout(() => {
                for (const i of wanted) setLoaded(i, true);
            }, 1000);
        });
    
        // Indexes rather than virtual items: For keys rows by value, and the items are new objects on every scroll
        const rows = () => virtualizer.getVirtualItems().flatMap((item) => (loaded[item.index] ? [item.index] : []));
    
        return (
            <div style={{ border: '1px solid var(--semi-color-border)', padding: '10px' }}>
                <section ref={(node) => (scroller = node)} aria-label="虚拟滚动列表" tabIndex={0} style={{ height: '500px', overflow: 'auto' }}>
                    <List style={{ position: 'relative', height: `${virtualizer.getTotalSize()}px` }} emptyContent={<Spin />}>
                        <For each={rows()}>
                            {(index) => (
                                <List.Item
                                    style={{ position: 'absolute', 'inset-inline': '0', top: `${index * rowHeight}px`, height: `${rowHeight}px` }}
                                    header={<Avatar color="grey">SE</Avatar>}
                                    main={
                                        <div>
                                            <span style={{ color: 'var(--semi-color-text-0)', 'font-weight': '500' }}>{dataList[index].title}</span>
                                            <p style={{ color: 'var(--semi-color-text-2)', margin: '4px 0' }}>Semi Design 设计系统包含设计语言以及一整套可复用的前端组件,帮助设计师与开发者更容易地打造高质量的、用户体验一致的、符合设计规范的 Web 应用。</p>
                                        </div>
                                    }
                                />
                            )}
                        </For>
                    </List>
                </section>
            </div>
        );
    }

    Drag Sort

    You can integrate the Solid build of dnd-kit (@dnd-kit/solid) to implement drag and drop sort. The dragged element is the li rendered by List.Item.

    • SE
      Semi Design Title 1

      Semi Design 设计系统包含设计语言以及一整套可复用的前端组件,帮助设计师与开发者更容易地打造高质量的、用户体验一致的、符合设计规范的 Web 应用。

    • SE
      Semi Design Title 2

      Semi Design 设计系统包含设计语言以及一整套可复用的前端组件,帮助设计师与开发者更容易地打造高质量的、用户体验一致的、符合设计规范的 Web 应用。

    • SE
      Semi Design Title 3

      Semi Design 设计系统包含设计语言以及一整套可复用的前端组件,帮助设计师与开发者更容易地打造高质量的、用户体验一致的、符合设计规范的 Web 应用。

    • SE
      Semi Design Title 4

      Semi Design 设计系统包含设计语言以及一整套可复用的前端组件,帮助设计师与开发者更容易地打造高质量的、用户体验一致的、符合设计规范的 Web 应用。

    • SE
      Semi Design Title 5

      Semi Design 设计系统包含设计语言以及一整套可复用的前端组件,帮助设计师与开发者更容易地打造高质量的、用户体验一致的、符合设计规范的 Web 应用。

    Code
    import { createSignal } from 'solid-js';
    import { RestrictToVerticalAxis } from '@dnd-kit/abstract/modifiers';
    import { Accessibility } from '@dnd-kit/dom';
    import { arrayMove } from '@dnd-kit/helpers';
    import { DragDropProvider } from '@dnd-kit/solid';
    import { isSortable, useSortable } from '@dnd-kit/solid/sortable';
    import { List, Avatar, type AvatarColor } from '@fibrous/semi-ui-solid';
    
    interface Item {
        id: number;
        title: string;
        color: AvatarColor;
    }
    
    const data: Item[] = [
        {
            id: 1,
            title: 'Semi Design Title 1',
            color: 'red',
        },
        {
            id: 2,
            title: 'Semi Design Title 2',
            color: 'grey',
        },
        {
            id: 3,
            title: 'Semi Design Title 3',
            color: 'light-green',
        },
        {
            id: 4,
            title: 'Semi Design Title 4',
            color: 'light-blue',
        },
        {
            id: 5,
            title: 'Semi Design Title 5',
            color: 'pink',
        },
    ];
    
    interface SortableListItemProps {
        item: Item;
        index: number;
    }
    
    function SortableListItem(props: SortableListItemProps) {
        const { ref, isDragging } = useSortable({
            get id() {
                return props.item.id;
            },
            get index() {
                return props.index;
            },
        });
    
        return (
            <List.Item
                ref={(node) => ref(node)}
                class={isDragging() ? 'isDragging' : undefined}
                style={{
                    border: '1px solid var(--semi-color-border)',
                    'margin-bottom': '12px',
                    cursor: 'grabbing',
                    ...(isDragging() ? { 'z-index': 999, position: 'relative', 'background-color': 'var(--semi-color-bg-0)' } : {}),
                }}
                header={<Avatar color={props.item.color}>SE</Avatar>}
                main={
                    <div>
                        <span style={{ color: 'var(--semi-color-text-0)', 'font-weight': '500' }}>{props.item.title}</span>
                        <p style={{ color: 'var(--semi-color-text-2)', margin: '4px 0' }}>Semi Design 设计系统包含设计语言以及一整套可复用的前端组件,帮助设计师与开发者更容易地打造高质量的、用户体验一致的、符合设计规范的 Web 应用。</p>
                    </div>
                }
            />
        );
    }
    
    export default function ListDemo() {
        const [listItems, setListItems] = createSignal(data);
    
        return (
            <div style={{ padding: '12px', border: '1px solid var(--semi-color-border)', margin: '12px' }}>
                <DragDropProvider
                    plugins={(defaults) => defaults.filter((plugin) => plugin !== Accessibility)}
                    modifiers={[RestrictToVerticalAxis]}
                    onDragEnd={(event) => {
                        const { source } = event.operation;
                        if (!event.canceled && isSortable(source)) {
                            setListItems((items) => arrayMove(items, source.initialIndex, source.index));
                        }
                    }}
                >
                    <List dataSource={listItems()} renderItem={(item, index) => <SortableListItem item={item} index={index} />} />
                </DragDropProvider>
            </div>
        );
    }

    With Pagination

    You can use Pagination in combination to achieve a paged List

    • 围城
    • 平凡的世界(全三册)
    • 三体(全集)
    • 雪中悍刀行(全集)
    • 1/3
    Code
    import { createSignal } from 'solid-js';
    import { List, Pagination } from '@fibrous/semi-ui-solid';
    
    const data = ['围城', '平凡的世界(全三册)', '三体(全集)', '雪中悍刀行(全集)', '撒哈拉的故事', '明朝那些事', '一禅小和尚', '沙丘', '被讨厌的勇气', '罪与罚', '月亮与六便士', '沉默的大多数'];
    
    const pageSize = 4;
    
    const getData = (page: number) => data.slice((page - 1) * pageSize, page * pageSize);
    
    export default function ListDemo() {
        const [page, setPage] = createSignal(1);
    
        return (
            <div>
                <div style={{ 'margin-right': '16px', width: '280px', display: 'flex', 'flex-wrap': 'wrap' }}>
                    <List dataSource={getData(page())} split={false} size="small" class="component-list-demo-booklist" style={{ border: '1px solid var(--semi-color-border)', 'flex-basis': '100%', 'flex-shrink': '0' }} renderItem={(item) => <List.Item class="list-item">{item}</List.Item>} />
                    <Pagination size="small" style={{ width: '100%', 'flex-basis': '100%', 'justify-content': 'center' }} pageSize={pageSize} total={data.length} currentPage={page()} onPageChange={(cPage) => setPage(cPage)} />
                </div>
            </div>
        );
    }

    With filter

    You can use it by assembling Input to filter the List

    • 围城
    • 平凡的世界(全三册)
    • 三体(全集)
    • 雪中悍刀行(全集)
    • 撒哈拉的故事
    • 明朝那些事
    • 一禅小和尚
    • 沙丘
    • 被讨厌的勇气
    • 罪与罚
    Code
    import { createSignal } from 'solid-js';
    import { IconSearch } from '@fibrous/semi-icons-solid';
    import { Input, List } from '@fibrous/semi-ui-solid';
    
    const data = ['围城', '平凡的世界(全三册)', '三体(全集)', '雪中悍刀行(全集)', '撒哈拉的故事', '明朝那些事', '一禅小和尚', '沙丘', '被讨厌的勇气', '罪与罚'];
    
    export default function ListDemo() {
        const [list, setList] = createSignal(data);
    
        const onSearch = (string?: string) => {
            setList(string ? data.filter((item) => item.includes(string)) : data);
        };
    
        return (
            <div>
                <div style={{ 'margin-right': '16px', width: '280px', display: 'flex', 'flex-wrap': 'wrap', border: '1px solid var(--semi-color-border)' }}>
                    <List
                        class="component-list-demo-booklist"
                        dataSource={list()}
                        split={false}
                        header={
                            <Input
                                onCompositionEnd={(event) => onSearch(event.currentTarget.value)}
                                onChange={(value) => {
                                    if (!value) {
                                        onSearch();
                                    }
                                }}
                                placeholder="搜索"
                                prefix={<IconSearch />}
                            />
                        }
                        size="small"
                        style={{ 'flex-basis': '100%', 'flex-shrink': '0', 'border-bottom': '1px solid var(--semi-color-border)' }}
                        renderItem={(item) => <List.Item class="list-item">{item}</List.Item>}
                    />
                </div>
            </div>
        );
    }

    Add delete item

    • 围城
    • 平凡的世界(全三册)
    • 三体(全集)
    • 雪中悍刀行(全集)
    • 撒哈拉的故事
    • 明朝那些事
    • 一禅小和尚
    • 沙丘
    新增书籍
    Code
    import { createSignal } from 'solid-js';
    import { IconMinusCircle, IconPlusCircle } from '@fibrous/semi-icons-solid';
    import { Button, List } from '@fibrous/semi-ui-solid';
    
    const data = ['围城', '平凡的世界(全三册)', '三体(全集)', '雪中悍刀行(全集)', '撒哈拉的故事', '明朝那些事', '一禅小和尚', '沙丘', '被讨厌的勇气', '罪与罚', '月亮与六便士', '沉默的大多数', '第一人称单数'];
    
    export default function ListDemo() {
        const [list, setList] = createSignal(data.slice(0, 8));
    
        const updateList = (item?: string) => {
            if (item) {
                setList(list().filter((i) => item !== i));
            } else {
                setList(list().concat(data.slice(list().length, list().length + 1)));
            }
        };
    
        return (
            <div>
                <div style={{ 'margin-right': '16px', width: '280px', display: 'flex', 'flex-wrap': 'wrap', border: '1px solid var(--semi-color-border)' }}>
                    <List
                        class="component-list-demo-booklist"
                        dataSource={list()}
                        split={false}
                        size="small"
                        style={{ 'flex-basis': '100%', 'flex-shrink': '0', 'border-bottom': '1px solid var(--semi-color-border)' }}
                        renderItem={(item) => (
                            <li style={{ margin: '4px' }} class="list-item">
                                <Button type="danger" theme="borderless" aria-label={`删除${item}`} icon={<IconMinusCircle />} onClick={() => updateList(item)} style={{ 'margin-right': '4px' }} />
                                {item}
                            </li>
                        )}
                    />
                    <div style={{ margin: '4px', 'font-size': '14px' }}>
                        <Button theme="borderless" aria-label="新增书籍" icon={<IconPlusCircle />} onClick={() => updateList()} style={{ 'margin-right': '4px', color: 'var(--semi-color-info)' }} />
                        新增书籍
                    </div>
                </div>
            </div>
        );
    }

    Single or multiple selection

    You can enhance the List into a list selector by combining Radio or Checkbox

    • 围城
    • 平凡的世界(全三册)
    • 三体(全集)
    • 雪中悍刀行(全集)
    • 撒哈拉的故事
    • 明朝那些事
    • 一禅小和尚
    • 沙丘
    Code
    import { createSignal } from 'solid-js';
    import { Checkbox, CheckboxGroup, List, Radio, RadioGroup, type CheckboxValue } from '@fibrous/semi-ui-solid';
    
    const data = ['围城', '平凡的世界(全三册)', '三体(全集)', '雪中悍刀行(全集)', '撒哈拉的故事', '明朝那些事', '一禅小和尚', '沙丘', '被讨厌的勇气', '罪与罚', '月亮与六便士', '沉默的大多数', '第一人称单数'];
    
    const dataSource = data.slice(0, 8);
    
    export default function ListDemo() {
        const [checkboxVal, setCV] = createSignal<CheckboxValue[]>([data[0]]);
        const [radioVal, setRV] = createSignal(data[0]);
    
        return (
            <div style={{ display: 'flex' }}>
                <div style={{ 'margin-right': '16px', width: '280px', display: 'flex', 'flex-wrap': 'wrap' }}>
                    <CheckboxGroup value={checkboxVal()} onChange={(value) => setCV(value)}>
                        <List
                            dataSource={dataSource}
                            class="component-list-demo-booklist"
                            split={false}
                            size="small"
                            style={{ border: '1px solid var(--semi-color-border)', 'flex-basis': '100%', 'flex-shrink': '0' }}
                            renderItem={(item) => (
                                <List.Item class="list-item">
                                    <Checkbox value={item}>{item}</Checkbox>
                                </List.Item>
                            )}
                        />
                    </CheckboxGroup>
                </div>
                <div style={{ 'margin-right': '16px', width: '280px', display: 'flex', 'flex-wrap': 'wrap' }}>
                    <RadioGroup value={radioVal()} onChange={(event) => setRV(event.target.value)}>
                        <List
                            class="component-list-demo-booklist"
                            dataSource={dataSource}
                            split={false}
                            size="small"
                            style={{ border: '1px solid var(--semi-color-border)', 'flex-basis': '100%', 'flex-shrink': '0' }}
                            renderItem={(item) => (
                                <List.Item class="list-item">
                                    <Radio value={item}>{item}</Radio>
                                </List.Item>
                            )}
                        />
                    </RadioGroup>
                </div>
            </div>
        );
    }

    Keyboard events

    You can monitor the keyboard events of the corresponding keys by yourself to realize the selection of different items. As in the following example, you can use the up and down arrow keys to select different items

    • 围城
    • 平凡的世界(全三册)
    • 三体(全集)
    • 雪中悍刀行(全集)
    • 撒哈拉的故事
    • 明朝那些事
    • 一禅小和尚
    • 沙丘
    • 被讨厌的勇气
    • 罪与罚
    Code
    import { createSignal, onCleanup, onMount } from 'solid-js';
    import { List } from '@fibrous/semi-ui-solid';
    
    const list = ['围城', '平凡的世界(全三册)', '三体(全集)', '雪中悍刀行(全集)', '撒哈拉的故事', '明朝那些事', '一禅小和尚', '沙丘', '被讨厌的勇气', '罪与罚'];
    
    export default function ListDemo() {
        const [hoverIndex, setHoverIndex] = createSignal(-1);
    
        const changeIndex = (offset: number) => {
            setHoverIndex((currentIndex) => {
                let index = currentIndex + offset;
                if (index < 0) {
                    index = list.length - 1;
                }
                if (index >= list.length) {
                    index = 0;
                }
                return index;
            });
        };
    
        const keydownHandler = (event: KeyboardEvent) => {
            switch (event.key) {
                case 'ArrowUp':
                    event.preventDefault();
                    changeIndex(-1);
                    break;
                case 'ArrowDown':
                    event.preventDefault();
                    changeIndex(1);
                    break;
                default:
                    break;
            }
        };
    
        onMount(() => {
            window.addEventListener('keydown', keydownHandler);
            onCleanup(() => window.removeEventListener('keydown', keydownHandler));
        });
    
        return (
            <div>
                <div style={{ 'margin-right': '16px', width: '280px', display: 'flex', 'flex-wrap': 'wrap', border: '1px solid var(--semi-color-border)' }}>
                    <List
                        class="component-list-demo-booklist"
                        dataSource={list}
                        split={false}
                        size="small"
                        style={{ 'flex-basis': '100%', 'flex-shrink': '0', 'border-bottom': '1px solid var(--semi-color-border)' }}
                        renderItem={(item, index) => <List.Item class={index === hoverIndex() ? 'component-list-demo-booklist-active-item' : ''}>{item}</List.Item>}
                    />
                </div>
            </div>
        );
    }

    The custom styles involved in the Demo of the above book list example are as follows

    .component-list-demo-booklist {
        .list-item {
            &:hover {
                background-color: var(--semi-color-fill-0);
            }
            &:active {
                background-color: var(--semi-color-fill-1);
            }
        }
    }
    
    .component-list-demo-booklist-active-item {
        background-color: var(--semi-color-fill-0);
    }

    Accessibility

    Pass aria-label or aria-labelledby to List to describe the list; the root element then gets role="group".

    A List.Item with onClick or onRightClick (including those set on the List) stays an <li> list item and renders a native <button type="button"> inside it as its only focus target, which screen readers announce as a button.

    • The button wraps header and main (or children when there are none) and its click area covers the whole row. After tabbing to it, Enter or Space triggers onClick and the context-menu key triggers onRightClick
    • extra is rendered outside the button on a higher layer, so buttons and links in it can be focused and clicked normally, and clicking them does not trigger the Item's onClick
    • When children is combined with header or main, children sits outside the button and is covered by the full-row button, so put interactive content in extra
    • A List.Item without a click callback renders no button and is not focusable

    A List.Item used outside a List renders as a plain list item without grid. With grid, the Row that holds the items has role="list" and the Col around each item has role="none", so screen readers read the items as members of that list.

    API reference

    List

    Props

    PropertyDescriptionTypeDefault
    borderedWhether to show a border.booleanfalse
    dataSourceData source of the list; each entry is rendered by renderItem.T[]-
    emptyContentContent shown when there is no data; the locale's empty text by default.JSX.Element-
    footerList footer.JSX.Element-
    gridGrid configuration: align, gutter, justify and type of Row, and span, offset, order, push, pull, the responsive breakpoints, class and style of Col. class and style land on the Col around each item.Grid-
    headerList header.JSX.Element-
    layoutList layout: vertical or horizontal.'vertical' | 'horizontal'"vertical"
    loadingWhether the list is loading; a Spin is shown when true.booleanfalse
    loadMoreLoad-more button.JSX.Element-
    onClickClick handler for the items that have no onClick of their own; an item with a handler renders a native button.ListItemEventHandler-
    onRightClickContext-menu handler for the items that have no onRightClick of their own; those items also render a native button.ListItemEventHandler-
    renderItemRenders each entry of dataSource, receiving the entry and its index and returning JSX, usually a List.Item.Type(item: T, ind: number) => JSX.Element-
    sizeList size: small, default or large.Size"default"
    splitWhether to show dividers between items.booleantrue

    class, style, ref and native attributes (such as data-*) land on the root element; when aria-label or aria-labelledby is passed without a role, the root element gets role="group". The payload of onClick and onRightClick is the native MouseEvent, and they act on the items, not on the root element.

    List grid props

    grid accepts only the Row props align, gutter, justify, type and the Col props span, offset, order, push, pull, the responsive breakpoints, class and style; other attributes are not accepted. class and style land on the Col around each item. Refer to Grid for their meaning.

    PropertiesInstructionstypeDefault
    spanNumber of grid spacesnumber-
    gutterGrid spacingnumber0
    xs< 576px responsive grid, a number or an object containing other attributesnumber|object-
    sm≥ 576px responsive grid, a number or an object containing other propertiesnumber|object-
    md≥ 768px responsive grid, a number or an object containing other propertiesnumber|object-
    lg≥ 992px responsive grid, a number or an object containing other propertiesnumber|object-
    xl≥ 1200px responsive grid, a number or an object containing other propertiesnumber|object-
    xxl≥ 1600px responsive grid, a number or an object containing other propertiesnumber|object-

    List.Item

    Props

    PropertyDescriptionTypeDefault
    alignVertical alignment of header and main: flex-start, flex-end, center, baseline or stretch; flex-start when omitted.'flex-start' | 'flex-end' | 'center' | 'baseline' | 'stretch'-
    extraAdditional item content, rendered outside the button; interactive elements in it stay focusable and clickable, and clicking them does not trigger the row's click.JSX.Element-
    headerItem header content.JSX.Element-
    mainItem main content.JSX.Element-
    onClickClick handler, taking precedence over the List's onClick; an item with a handler renders a native button.ListItemEventHandler-
    onRightClickContext-menu handler, taking precedence over the List's onRightClick; an item with only this handler also renders a native button.ListItemEventHandler-

    class, style, ref and native attributes (such as data-*) land on the li, not on the outer Col in grid mode. When clickable, aria-* and tabIndex land on the inner button instead, and ref.focus() moves focus to that button.

    Content Guidelines

    • Capitalize the first letter
    • do not follow punctuation at the end
    • Grammatical parallelism: mixed use of active and passive, declarative and imperative sentences

    Design Tokens

    These are the component's internal Sass variables and cannot be overridden. To customize styles, override the --semi-* CSS variables (see the Design Token page) or write unlayered CSS rules.
    Sass variableValueUsage
    $color-list_default-border-defaultvar(--semi-color-border)列表描边颜色
    $color-list_empty-text-defaultvar(--semi-color-text-2)空状态下列表文字颜色
    $spacing-list_empty-paddingX0px空状态列表水平内边距
    $spacing-list_empty-paddingY$spacing-base-tight空状态列表垂直内边距
    $spacing-list_footer-paddingX$spacing-base-tight列表 footer 水平内边距
    $spacing-list_footer-paddingY$spacing-loose列表 footer 垂直内边距
    $spacing-list_item-paddingX$spacing-base-tight列表 item 水平内边距 - 默认尺寸
    $spacing-list_item-paddingY$spacing-loose列表 item 垂直内边距 - 默认尺寸
    $spacing-list_small-paddingX$spacing-tight列表 item 水平内边距 - 小尺寸
    $spacing-list_small-paddingY$spacing-base列表 item 垂直内边距 - 小尺寸
    $spacing-list_large-paddingX$spacing-base列表 item 水平内边距 - 大尺寸
    $spacing-list_large-paddingY$spacing-loose列表 item 垂直内边距 - 大尺寸
    $spacing-list_header-marginRight$spacing-base-loose列表 header 右侧外边距
    $spacing-list_extra-marginLeft$spacing-super-loose列表 extra 额外内容左侧外边距