--- localeCode: en-US order: 63 category: Show title: List subTitle: List icon: doc-list dir: column brief: Lists display a set of related contents --- ## Demos ### How to import ```jsx import import { List } from '@douyinfe/semi-ui'; ``` ### Basic Usage You can use `size` to size list. Supported values include `large`, `default`, `Small`. Header and Footer customized. ```jsx live=true dir="column" noInline=true import React from 'react'; import { List } from '@douyinfe/semi-ui'; class SimpleList extends React.Component { render() { const data = [ 'Do not go gentle into that good night,', 'Old age should burn and rave at close of day;', 'Rage, rage against the dying of the light.', ]; return (

Default Size

Header
} footer={
Footer
} bordered dataSource={data} renderItem={item => {item}} />

Small Size

Header
} footer={
Footer
} bordered dataSource={data} renderItem={item => {item}} />

Large Size

Header
} footer={
Footer
} bordered dataSource={data} renderItem={item => {item}} /> ); } } render(SimpleList); ``` ### 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` . ```jsx live=true dir="column" noInline=true import React from 'react'; import { List, ButtonGroup, Button, Avatar } from '@douyinfe/semi-ui'; class ContentList extends React.Component { render() { const data = [ // eslint-disable-next-line react/jsx-key

{`Life's but a walking shadow, a poor player, that struts and frets his hour upon the stage, and then is heard no more; it is a tale told by an idiot, full of sound and fury, signifying nothing.`}

, // eslint-disable-next-line react/jsx-key

Come what come may, time and the hour run through the roughest day.

, // eslint-disable-next-line react/jsx-key

{`Where shall we three meet again in thunder, lightning, or in rain? When the hurlyburly's done, when the battle's lost and won`}

, ]; return (
( SE} main={
Example {item}
} extra={ } /> )} />
); } } render(ContentList); ``` ### Layout Use `layout` property to set list layout, one of `vertical`(default) or `horizontal`. ```jsx live=true dir="column" noInline=true import React from 'react'; import { List, Avatar } from '@douyinfe/semi-ui'; class LayoutList extends React.Component { render() { const data = [ { title: 'Title 1', color: 'light-blue', }, { title: 'Title 2', color: 'grey', }, { title: 'Title 3', color: 'light-green', }, ]; return (
( SE} main={
{item.title}

{` Life's but a walking shadow, a poor player, that struts and frets his hour upon the stage, and then is heard no more; it is a tale told by an idiot, full of sound and fury, signifying nothing.`}

} /> )} />
); } } render(LayoutList); ``` ### 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. ```jsx live=true dir="column" noInline=true import React from 'react'; import { List, Descriptions, Rating, ButtonGroup, Button } from '@douyinfe/semi-ui'; class LayoutList extends React.Component { render() { const data = [ { title: 'Platform A', rating: 4.5, feedbacks: 124, }, { title: 'Platform B', rating: 4, feedbacks: 108, }, { title: 'Platform C', rating: 4.5, feedbacks: 244, }, { title: 'Platform D', feedbacks: 189, }, ]; const style = { border: '1px solid var(--semi-color-border)', backgroundColor: 'var(--semi-color-bg-2)', borderRadius: '3px', paddingLeft: '20px', }; return (
(

{item.title}

}, { key: 'Feedbacks', value: item.feedbacks }, ]} />
)} />
); } } render(LayoutList); ``` ### Responsive List Refer to [Grid](/en-US/basic/grid) for responsive dimensions. ```jsx live=true dir="column" noInline=true import React from 'react'; import { List, Descriptions, Rating, ButtonGroup, Button } from '@douyinfe/semi-ui'; class Responsive extends React.Component { render() { const data = [ { title: 'Platform A', rating: 4.5, feedbacks: 124, }, { title: 'Platform B', rating: 4, feedbacks: 108, }, { title: 'Platform C', rating: 3.5, feedbacks: 244, }, { title: 'Platform D', feedbacks: 189, }, { title: 'Platform E', rating: 3, feedbacks: 128, }, { title: 'Platform D', rating: 4, feedbacks: 156, }, ]; const style = { border: '1px solid var(--semi-color-border)', backgroundColor: 'var(--semi-color-bg-2)', borderRadius: '3px', paddingLeft: '20px', margin: '8px 2px', }; return (
(

{item.title}

}, { key: 'Feedbacks', value: item.feedbacks }, ]} />
)} />
); } } render(Responsive); ``` ### Load More You can use `loadMore` to achieve loading state for more incoming contents. ```jsx live=true dir="column" noInline=true import React from 'react'; import { List, Skeleton, Button, Avatar } from '@douyinfe/semi-ui'; class LoadMoreList extends React.Component { constructor() { super(); const count = 3; const data = []; for (let i = 0; i < 40; i++) { data.push({ color: 'grey', title: `Semi Design Title ${i}`, loading: false, }); } this.data = data; this.count = 0; this.fetchData = () => { let placeholders = [0, 1, 2].map(key => ({ loading: true })); this.setState({ loading: true, list: [...this.state.dataSource, ...placeholders], }); return new Promise((res, rej) => { setTimeout(() => { let dataSource = this.data.slice(this.count * count, this.count * count + count); res(dataSource); }, 1000); }).then(dataSource => { let newData = [...this.state.dataSource, ...dataSource]; this.setState({ loading: false, dataSource: newData, list: newData, noMore: !dataSource.length, }); }); }; this.state = { loading: false, dataSource: [], list: [], noMore: false, }; } componentDidMount() { this.fetchData(); } onLoadMore() { this.count++; this.fetchData(); } render() { const { loading, list, noMore } = this.state; const loadMore = !loading && !noMore ? (
) : null; const placeholder = (
); return ( ( SE} main={
{item.title}

Create a consistent, good-looking, easy-to-use, and efficient user experience with a user-centric, content-first, and human-friendly design system

} />
)} /> ); } } render(LoadMoreList); ``` ### Scroll to Load You can integrate [react-infinite-scroller](https://github.com/CassetteRocks/react-infinite-scroller) to implement scrolling load list. Recommended interaction could be reveal a loadmore button after three scrolling loads. ```jsx live=true dir="column" noInline=true hideInDSM import React from 'react'; import { List, Avatar, Spin, Button } from '@douyinfe/semi-ui'; import InfiniteScroll from 'react-infinite-scroller'; class ScrollLoad extends React.Component { constructor() { super(); const count = 5; const dataList = []; for (let i = 0; i < 100; i++) { dataList.push({ color: 'grey', title: `Semi Design Title ${i}`, loading: false, }); } this.data = dataList; this.count = 0; this.fetchData = () => { this.setState({ loading: true, }); return new Promise((res, rej) => { setTimeout(() => { let dataSource = this.data.slice(this.count * count, this.count * count + count); res(dataSource); }, 1000); }).then(dataSource => { let newData = [...this.state.dataSource, ...dataSource]; this.count++; this.setState({ loading: false, dataSource: newData, noMore: !dataSource.length, }); }); }; this.state = { loading: false, dataSource: [], hasMore: true, }; } componentDidMount() { this.fetchData(); } render() { const { loading, dataSource, hasMore } = this.state; const showLoadMore = this.count % 4 === 0; const loadMore = !loading && hasMore && showLoadMore ? (
) : null; return (
( SE} main={
{item.title}

Create a consistent, good-looking, easy-to-use, and efficient user experience with a user-centric, content-first, and human-friendly design system

} /> )} /> {this.state.loading && this.state.hasMore && (
)}
); } } render(ScrollLoad); ``` ### Scroll to Load Infinite Lists You can integrate [react-virtualized](https://github.com/bvaughn/react-virtualized) to implement infinite scrolling lists with virtualization to improve the performance for large amounts of data. ```jsx live=true dir="column" noInline=true hideInDSM import React from 'react'; import { List, Avatar } from '@douyinfe/semi-ui'; import { InfiniteLoader, AutoSizer } from 'react-virtualized'; import VList from 'react-virtualized/dist/commonjs/List'; class VirtualizedScroll extends React.Component { constructor() { super(); const dataList = []; for (let i = 0; i < 50; i++) { dataList.push({ color: 'grey', title: `Semi Design Title ${i}`, }); } this.data = dataList; this.fetchData = (startIndex, stopIndex) => { return new Promise((res, rej) => { setTimeout(() => { let dataSource = this.data.slice(startIndex, stopIndex + 1); res(dataSource); }, 1000); }).then(dataSource => { let newData = [...this.state.dataSource, ...dataSource]; const { loadedRowsMap, loadingRowCount } = this.state; const increment = stopIndex - startIndex + 1; for (let i = startIndex; i <= stopIndex; i++) { loadedRowsMap[i] = this.statusLoaded; } this.setState({ dataSource: newData, loadedRowsMap, loadingRowCount: loadingRowCount - increment, }); }); }; this.state = { dataSource: [], loadedRowsMap: {}, loadingRowCount: 0, }; this.statusLoading = 0; this.statusLoaded = 1; this.loadLimit = this.data.length; this.renderItem = this.renderItem.bind(this); this.fetchData = this.fetchData.bind(this); this.handleInfiniteOnLoad = this.handleInfiniteOnLoad.bind(this); this.isRowLoaded = this.isRowLoaded.bind(this); } handleInfiniteOnLoad({ startIndex, stopIndex }) { let { dataSource, loadedRowsMap, loadingRowCount } = this.state; const increment = stopIndex - startIndex + 1; if (stopIndex >= this.loadLimit || loadingRowCount > 0) { return; } for (let i = startIndex; i <= stopIndex; i++) { loadedRowsMap[i] = this.statusLoading; } this.setState({ loadingRowCount: loadingRowCount + increment, }); return this.fetchData(startIndex, stopIndex); } isRowLoaded({ index }) { const { loadedRowsMap } = this.state; return !!loadedRowsMap[index]; } renderItem({ index, key, style }) { const { dataSource, loadedRowsMap } = this.state; const item = dataSource[index]; if (!item) { return; } const content = ( SE} main={
{item.title}

Create a consistent, good-looking, easy-to-use, and efficient user experience with a user-centric, content-first, and human-friendly design system

} /> ); return content; } render() { const { dataSource } = this.state; const height = 500; return ( {({ onRowsRendered, registerChild }) => ( {({ width }) => ( )} )} ); } } render(VirtualizedScroll); ``` ### Drag Sort You can integrate [react-dnd](https://github.com/react-dnd/react-dnd) to implement drag and drop sort. ```jsx live=true dir="column" noInline=true hideInDSM import React from 'react'; import { List, Avatar } from '@douyinfe/semi-ui'; import { DndProvider, DragSource, DropTarget } from 'react-dnd'; import HTML5Backend from 'react-dnd-html5-backend'; import ReactDOM from 'react-dom'; class DraggableItem extends React.Component { render() { const { component, draggingItem, index, connectDragSource, connectDropTarget } = this.props; const opacity = draggingItem && draggingItem.index === index ? 0.3 : 1; const style = { border: '1px solid var(--semi-color-border)', marginBottom: 12, backgroundColor: 'var(--semi-color-bg-2)', cursor: 'move', }; return connectDragSource( connectDropTarget(
(this.node = node)} style={{ ...style, opacity }}> {component}
) ); } } const cardSource = { beginDrag(props) { return { id: props.id, index: props.index, }; }, }; const cardTarget = { hover(props, monitor, component) { const dragIndex = monitor.getItem().index; const hoverIndex = props.index; if (dragIndex === hoverIndex) { return; } const hoverBoundingRect = ReactDOM.findDOMNode(component).getBoundingClientRect(); const hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2; const clientOffset = monitor.getClientOffset(); const hoverClientY = clientOffset.y - hoverBoundingRect.top; if (dragIndex < hoverIndex && hoverClientY < hoverMiddleY) { return; } if (dragIndex > hoverIndex && hoverClientY > hoverMiddleY) { return; } monitor.getItem().index = hoverIndex; props.moveItem(dragIndex, hoverIndex); }, }; function collectDragSource(connect, monitor) { return { connectDragSource: connect.dragSource(), draggingItem: monitor.getItem(), }; } function collectDropTarget(connect) { return { connectDropTarget: connect.dropTarget(), }; } DraggableItem = DragSource('item', cardSource, collectDragSource)(DraggableItem); DraggableItem = DropTarget('item', cardTarget, collectDropTarget)(DraggableItem); class DraggableList extends React.Component { constructor() { const listItems = [ { title: 'Semi Design Title 1', color: 'red', }, { title: 'Semi Design Title 2', color: 'grey', }, { title: 'Semi Design Title 3', color: 'light-green', }, { title: 'Semi Design Title 4', color: 'light-blue', }, { title: 'Semi Design Title 5', color: 'pink', }, ]; super(); this.state = { data: listItems, }; this.moveItem = this.moveItem.bind(this); this.renderDraggable = this.renderDraggable.bind(this); } moveItem(dragIndex, hoverIndex) { const { data } = this.state; let temp = data[dragIndex]; data[dragIndex] = data[hoverIndex]; data[hoverIndex] = temp; this.setState( { ...this.state, data } ); } renderDraggable(item, id) { const content = ( SE} main={
{item.title}

Create a consistent, good-looking, easy-to-use, and efficient user experience with a user-centric, content-first, and human-friendly design system

} /> ); return ( ); } render() { const { data } = this.state; return (
); } } render(DraggableList); ``` ### With Pagination You can use Pagination in combination to achieve a paged List ```jsx live=true dir="column" hideInDSM import React, { useState } from 'react'; import { List, Pagination } from '@douyinfe/semi-ui'; () => { const data = [ 'Siege', 'The ordinary world', 'Three Body', 'Snow in the Snow', 'Saharan story', 'Those things in the Ming Dynasty', 'A little monk of Zen', 'Dune', 'The courage to be hated', 'Crime and Punishment', 'Moon and sixpence', 'The silent majority', 'First person singular', ]; const [page, onPageChange] = useState(1); let pageSize = 4; const getData = (page) => { let start = (page - 1) * pageSize; let end = page * pageSize; return data.slice(start, end); }; return (
{item}} /> onPageChange(cPage)} />
); }; ``` ### With filter You can use it by assembling Input to filter the List ```jsx live=true dir="column" hideInDSM import React, { useState } from 'react'; import { List, Input } from '@douyinfe/semi-ui'; import { IconSearch } from '@douyinfe/semi-icons'; () => { const data = [ 'Siege', 'The ordinary world', 'Three Body', 'Snow in the Snow', 'Saharan story', 'Those things in the Ming Dynasty', 'A little monk of Zen', 'Dune', 'The courage to be hated', 'Crime and Punishment', ]; const [list, setList] = useState(data); const onSearch = (string) => { let newList; if (string) { newList = data.filter(item => item.includes(string)); } else { newList = data; } setList(newList); }; return (
onSearch(v.target.value)} onChange={(v) => !v ? onSearch() : null} placeholder='search' prefix={} />} size='small' style={{ flexBasis: '100%', flexShrink: 0, borderBottom: '1px solid var(--semi-color-border)' }} renderItem={item => {item} } />
); }; ``` ### Add delete item ```jsx live=true dir="column" hideInDSM import React, { useState } from 'react'; import { List, Input, Button } from '@douyinfe/semi-ui'; import { IconMinusCircle, IconPlusCircle } from '@douyinfe/semi-icons'; () => { const data = [ 'Siege', 'The ordinary world', 'Three Body', 'Snow in the Snow', 'Saharan story', 'Those things in the Ming Dynasty', 'A little monk of Zen', 'Dune', 'The courage to be hated', 'Crime and Punishment', 'Moon and sixpence', 'The silent majority', 'First person singular', ]; const [list, setList] = useState(data.slice(0, 8)); const updateList = (item) => { let newList; if (item) { newList = list.filter(i => item !== i); } else { newList = list.concat(data.slice(list.length, list.length + 1)); } setList(newList); }; return (
} />
updateList()}> Add book
); }; ``` ### Single or multiple selection You can enhance the List into a list selector by combining Radio or Checkbox ```jsx live=true dir="column" hideInDSM import React, { useState } from 'react'; import { List, Input, Button, Checkbox, Radio, RadioGroup, CheckboxGroup } from '@douyinfe/semi-ui'; () => { const data = [ 'Siege', 'The ordinary world', 'Three Body', 'Snow in the Snow', 'Saharan story', 'Those things in the Ming Dynasty', 'A little monk of Zen', 'Dune', 'The courage to be hated', 'Crime and Punishment', 'Moon and sixpence', 'The silent majority', 'First person singular', ]; const [page, onPageChange] = useState(1); const [checkboxVal, setCV] = useState([...data[0]]); const [radioVal, setRV] = useState(data[0]); let pageSize = 8; const getData = (page) => { let start = (page - 1) * pageSize; let end = page * pageSize; return data.slice(start, end); }; return (
setCV(value)}> {item}} />
setRV(e.target.value)}> {item}} />
); }; ``` ### 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 ```jsx live=true dir="column" hideInDSM import React, { useState, useRef } from 'react'; import { List, Input, Button } from '@douyinfe/semi-ui'; () => { const data = [ 'Siege', 'The ordinary world', 'Three Body', 'Snow in the Snow ', 'Saharan story', 'Those things in the Ming Dynasty', 'A little monk of Zen', 'Dune', 'The courage to be hated', 'Crime and Punishment', 'Moon and sixpence', 'The silent majority', 'First person singular', ]; const [list, setList] = useState(data.slice(0, 10)); const [hoverIndex, setHi] = useState(-1); const i = useRef(-1); let changeIndex = (offset) => { let currentIndex = i.current; let index = currentIndex + offset; if (index < 0) { index = list.length - 1; } if (index >= list.length) { index = 0; } i.current = index; setHi(index); }; useEffect(() => { let keydownHandler = (event) => { let key = event.keyCode; switch (key) { case 38: // KeyCode.UP event.preventDefault(); changeIndex(-1); break; case 40: // KeyCode.DOWN event.preventDefault(); changeIndex(1); break; default: break; } }; window.addEventListener('keydown', keydownHandler); return () => { window.removeEventListener('keydown', keydownHandler); }; }, []); return (
{item} } />
); }; ``` The custom styles involved in the Demo of the above book list example are as follows ```scss .component-list-demo-booklist { .list-item { &:hover { background-color: var(--semi-color-fill-0); } &:active { background-color: var(--semi-color-fill-1); } } } body > .component-list-demo-drag-item { font-size: 14px; } .component-list-demo-booklist-active-item { background-color: var(--semi-color-fill-0); } ``` ## API reference ### List | Properties | Instructions | type | Default | | ------------ | ------------------------------------------------------------------ | -------------------------------- | ---------- | | bordered | Toggle whether to display border | boolean | `false` | | className | Class name | string | - | | dataSource | List data source | any[] | - | | emptyContent | Displayed content when empty | ReactNode | - | | footer | Footer of list | ReactNode | - | | grid | Grid configuration | [Grid](/en-US/basic/grid#API-reference) | - | | header | Header of list | ReactNode | - | | layout | Layout, one of `vertical`, `vertical` | string | `vertical` | | loadMore | Loadmore button | ReactNode | - | | loading | Toggle whether to display `Spin` when loading | boolean | `false` | | renderItem | When using dataSource, you can customize rendering with renderItem | (item, ind) => ReactNode | - | | size | Size, one of `small`, `default`, `large` | string | `default` | | split | Toggle whether to display split line | boolean | `true` | | style | Inline style | CSSProperties | - | | onClick | Callback function when click an item **v>=1.0.0** | function | - | | onRightClick | Callback function when right click an item **v>=1.0.0** | function | - | ### Listgrid props **v>=1.7.0** Other grid properties are also supported. Refer to [Grid](/en-US/basic/grid). | Properties | Instructions | type | Default | | ---------- | ----------------------------------------------------------------------------- | -------------- | ------- | | span | Number of grid spaces | number | - | | gutter | Grid spacing | number | 0 | | xs | `< 576px` responsive grid, a number or an object containing other attributes | number\|object | - | | sm | `≥ 576px` responsive grid, a number or an object containing other properties | number\|object | - | | md | `≥ 768px` responsive grid, a number or an object containing other properties | number\|object | - | | lg | `≥ 992px` responsive grid, a number or an object containing other properties | number\|object | - | | xl | `≥ 1200px` responsive grid, a number or an object containing other properties | number\|object | - | | xxl | `≥ 1600px` responsive grid, a number or an object containing other properties | number\|object | - | ### List.Item | Properties | Instructions | type | Default | | ------------ | ------------------------------------------------------------------------------------------------------- | --------- | ------------ | | align | Vertical alignment of header and main, one of `flex-start`, `flex-end`, `center`, `baseline`, `stretch` | string | `flex-start` | | className | Class name | string | - | | extra | Additional content | ReactNode | - | | header | List item header content | ReactNode | - | | main | List item body content | ReactNode | - | | onClick | Callback function when click an item **v>=1.0.0** | function | - | | onRightClick | Callback function when right click an item **v>=1.0.0** | function | - | | style | Inline style | CSSProperties | - | ## 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