Basic · Layout

Layout

Used to quickly divide the overall layout of the page

Overview

  • Layout: Layout containers. You can nest Header Sider Content Footer or Layout itself inside.
  • Header: Head component, can only be used inside Layout.
  • Sider: Sidebar, can only be used inside Layout.
  • Content: Content component, can only be used inside Layout.
  • Footer: Footer component, can only be used inside Layout.

Demos

How to import

import { Layout } from '@fibrous/semi-ui-solid';

Use the subcomponents as Layout.Header, Layout.Sider, Layout.Content and Layout.Footer.

Three-section Layout

Header
Content
Footer
Code
import { Layout } from '@fibrous/semi-ui-solid';

const commonStyle = {
    height: '64px',
    'line-height': '64px',
    background: 'var(--semi-color-fill-0)',
};

export default function LayoutDemo() {
    return (
        <Layout class="components-layout-demo">
            <Layout.Header style={commonStyle}>Header</Layout.Header>
            <Layout.Content style={{ height: '300px', 'line-height': '300px' }}>Content</Layout.Content>
            <Layout.Footer style={commonStyle}>Footer</Layout.Footer>
        </Layout>
    );
}
Header
Content
Footer
Code
import { Layout } from '@fibrous/semi-ui-solid';

const commonStyle = {
    height: '64px',
    'line-height': '64px',
    background: 'var(--semi-color-fill-0)',
};

export default function LayoutDemo() {
    return (
        <Layout class="components-layout-demo">
            <Layout.Header style={commonStyle}>Header</Layout.Header>
            <Layout>
                <Layout.Sider style={{ width: '120px', background: 'var(--semi-color-fill-2)' }}>Sider</Layout.Sider>
                <Layout.Content style={{ height: '300px', 'line-height': '300px' }}>Content</Layout.Content>
            </Layout>
            <Layout.Footer style={commonStyle}>Footer</Layout.Footer>
        </Layout>
    );
}
Header
Content
Footer
Code
import { Layout } from '@fibrous/semi-ui-solid';

const commonStyle = {
    height: '64px',
    'line-height': '64px',
    background: 'var(--semi-color-fill-0)',
};

export default function LayoutDemo() {
    return (
        <Layout class="components-layout-demo">
            <Layout.Header style={commonStyle}>Header</Layout.Header>
            <Layout>
                <Layout.Content style={{ height: '300px', 'line-height': '300px' }}>Content</Layout.Content>
                <Layout.Sider style={{ width: '120px', background: 'var(--semi-color-fill-2)' }}>Sider</Layout.Sider>
            </Layout>
            <Layout.Footer style={commonStyle}>Footer</Layout.Footer>
        </Layout>
    );
}
Header
Content
Footer
Code
import { Layout } from '@fibrous/semi-ui-solid';

const commonStyle = {
    height: '64px',
    'line-height': '64px',
    background: 'var(--semi-color-fill-0)',
};

export default function LayoutDemo() {
    return (
        <Layout class="components-layout-demo">
            <Layout.Sider style={{ width: '120px', background: 'var(--semi-color-fill-2)' }}>Sider</Layout.Sider>
            <Layout>
                <Layout.Header style={commonStyle}>Header</Layout.Header>
                <Layout.Content style={{ height: '300px', 'line-height': '300px' }}>Content</Layout.Content>
                <Layout.Footer style={commonStyle}>Footer</Layout.Footer>
            </Layout>
        </Layout>
    );
}

Responsive Layout

Six response sizes are preset in the sidebar: xs,sm,md,lg,xl,xxl. You can use breakpoint to set breakpoints.

Use onBreakpoint to call callback functions.

Header
Content
Footer
Code
import { Layout, type SiderProps } from '@fibrous/semi-ui-solid';

const commonStyle = {
    height: '64px',
    'line-height': '64px',
    background: 'var(--semi-color-fill-0)',
};

export default function LayoutDemo() {
    const onBreakpoint: SiderProps['onBreakpoint'] = (screen, bool) => {
        console.log(screen, bool);
    };
    return (
        <Layout class="components-layout-demo">
            <Layout.Header style={commonStyle}>Header</Layout.Header>
            <Layout>
                <Layout.Sider style={{ width: '120px', background: 'var(--semi-color-fill-2)' }} breakpoint={['md']} onBreakpoint={onBreakpoint}>
                    Sider
                </Layout.Sider>
                <Layout.Content style={{ height: '300px', 'line-height': '300px' }}>Content</Layout.Content>
            </Layout>
            <Layout.Footer style={commonStyle}>Footer</Layout.Footer>
        </Layout>
    );
}

Layout Examples

Top-nav Layout

Copyright © 2023 ByteDance. All Rights Reserved. 平台客服反馈建议
Code
import { IconBell, IconBytedanceLogo, IconHelpCircle, IconHome, IconLive, IconSemiLogo, IconSetting } from '@fibrous/semi-icons-solid';
import { Avatar, Breadcrumb, Button, Layout, Nav, Skeleton } from '@fibrous/semi-ui-solid';

export default function LayoutDemo() {
    return (
        <Layout style={{ border: '1px solid var(--semi-color-border)' }}>
            <Layout.Header style={{ 'background-color': 'var(--semi-color-bg-1)' }}>
                <div>
                    <Nav mode="horizontal" defaultSelectedKeys={['Home']}>
                        <Nav.Header>
                            <IconSemiLogo style={{ 'font-size': '36px' }} />
                        </Nav.Header>
                        <Nav.Item itemKey="Home" text="首页" icon={<IconHome size="large" />} />
                        <Nav.Item itemKey="Live" text="直播" icon={<IconLive size="large" />} />
                        <Nav.Item itemKey="Setting" text="设置" icon={<IconSetting size="large" />} />
                        <Nav.Footer>
                            <Button theme="borderless" aria-label="通知" icon={<IconBell size="large" />} style={{ color: 'var(--semi-color-text-2)', 'margin-right': '12px' }} />
                            <Button theme="borderless" aria-label="帮助" icon={<IconHelpCircle size="large" />} style={{ color: 'var(--semi-color-text-2)', 'margin-right': '12px' }} />
                            <Avatar color="orange" size="small">
                                YJ
                            </Avatar>
                        </Nav.Footer>
                    </Nav>
                </div>
            </Layout.Header>
            <Layout.Content style={{ padding: '24px', 'background-color': 'var(--semi-color-bg-0)' }}>
                <Breadcrumb style={{ 'margin-bottom': '24px' }} routes={['首页', '当这个页面标题很长时需要省略', '上一页', '详情页']} />
                <div style={{ 'border-radius': '10px', border: '1px solid var(--semi-color-border)', height: '376px', padding: '32px' }}>
                    <Skeleton placeholder={<Skeleton.Paragraph rows={2} />} loading>
                        <p>Hi, Bytedance dance dance.</p>
                        <p>Hi, Bytedance dance dance.</p>
                    </Skeleton>
                </div>
            </Layout.Content>
            <Layout.Footer style={{ display: 'flex', 'justify-content': 'space-between', padding: '20px', color: 'var(--semi-color-text-2)', 'background-color': 'var(--semi-grey-0)' }}>
                <span style={{ display: 'flex', 'align-items': 'center' }}>
                    <IconBytedanceLogo size="large" style={{ 'margin-right': '8px' }} />
                    <span>Copyright © 2023 ByteDance. All Rights Reserved. </span>
                </span>
                <span>
                    <span style={{ 'margin-right': '24px' }}>平台客服</span>
                    <span>反馈建议</span>
                </span>
            </Layout.Footer>
        </Layout>
    );
}

Top-Nav SideBar Layout

Copyright © 2023 ByteDance. All Rights Reserved. 平台客服反馈建议
Code
import { Dynamic } from 'solid-js/web';
import { IconBell, IconBytedanceLogo, IconHelpCircle, IconHistogram, IconHome, IconLive, IconSemiLogo, IconSetting } from '@fibrous/semi-icons-solid';
import { Avatar, Breadcrumb, Button, Layout, Nav, Skeleton } from '@fibrous/semi-ui-solid';

const icons: Record<string, typeof IconHome> = { Home: IconHome, Histogram: IconHistogram, Live: IconLive, Setting: IconSetting };

export default function LayoutDemo() {
    return (
        <Layout style={{ border: '1px solid var(--semi-color-border)' }}>
            <Layout.Header style={{ 'background-color': 'var(--semi-color-bg-1)' }}>
                <div>
                    <Nav aria-label="顶部导航" mode="horizontal" defaultSelectedKeys={['Home']}>
                        <Nav.Header>
                            <IconSemiLogo style={{ height: '36px', 'font-size': '36px' }} />
                        </Nav.Header>
                        <span style={{ color: 'var(--semi-color-text-2)' }}>
                            <span style={{ 'margin-right': '24px', color: 'var(--semi-color-text-0)', 'font-weight': '600' }}>模版推荐</span>
                            <span style={{ 'margin-right': '24px' }}>所有模版</span>
                            <span>我的模版</span>
                        </span>
                        <Nav.Footer>
                            <Button theme="borderless" aria-label="通知" icon={<IconBell size="large" />} style={{ color: 'var(--semi-color-text-2)', 'margin-right': '12px' }} />
                            <Button theme="borderless" aria-label="帮助" icon={<IconHelpCircle size="large" />} style={{ color: 'var(--semi-color-text-2)', 'margin-right': '12px' }} />
                            <Avatar color="orange" size="small">
                                YJ
                            </Avatar>
                        </Nav.Footer>
                    </Nav>
                </div>
            </Layout.Header>
            <Layout>
                <Layout.Sider style={{ 'background-color': 'var(--semi-color-bg-1)' }}>
                    <Nav
                        aria-label="侧边导航"
                        style={{ 'max-width': '220px', height: '100%' }}
                        defaultSelectedKeys={['Home']}
                        items={[
                            { itemKey: 'Home', text: '首页' },
                            { itemKey: 'Histogram', text: '基础数据' },
                            { itemKey: 'Live', text: '测试功能' },
                            { itemKey: 'Setting', text: '设置' },
                        ]}
                        renderItemIcon={({ item }) => <Dynamic component={icons[String(item.itemKey)]} size="large" />}
                        footer={{
                            collapseButton: true,
                        }}
                    />
                </Layout.Sider>
                <Layout.Content style={{ padding: '24px', 'background-color': 'var(--semi-color-bg-0)' }}>
                    <Breadcrumb style={{ 'margin-bottom': '24px' }} routes={['首页', '当这个页面标题很长时需要省略', '上一页', '详情页']} />
                    <div style={{ 'border-radius': '10px', border: '1px solid var(--semi-color-border)', height: '376px', padding: '32px' }}>
                        <Skeleton placeholder={<Skeleton.Paragraph rows={2} />} loading>
                            <p>Hi, Bytedance dance dance.</p>
                            <p>Hi, Bytedance dance dance.</p>
                        </Skeleton>
                    </div>
                </Layout.Content>
            </Layout>
            <Layout.Footer style={{ display: 'flex', 'justify-content': 'space-between', padding: '20px', color: 'var(--semi-color-text-2)', 'background-color': 'var(--semi-grey-0)' }}>
                <span style={{ display: 'flex', 'align-items': 'center' }}>
                    <IconBytedanceLogo size="large" style={{ 'margin-right': '8px' }} />
                    <span>Copyright © 2023 ByteDance. All Rights Reserved. </span>
                </span>
                <span>
                    <span style={{ 'margin-right': '24px' }}>平台客服</span>
                    <span>反馈建议</span>
                </span>
            </Layout.Footer>
        </Layout>
    );
}
Copyright © 2019 ByteDance. All Rights Reserved. 平台客服反馈建议
Code
import { Dynamic } from 'solid-js/web';
import { IconBell, IconBytedanceLogo, IconHelpCircle, IconHistogram, IconHome, IconLive, IconSemiLogo, IconSetting } from '@fibrous/semi-icons-solid';
import { Avatar, Breadcrumb, Button, Layout, Nav, Skeleton } from '@fibrous/semi-ui-solid';

const items = [
    { itemKey: 'Home', text: '首页' },
    { itemKey: 'Histogram', text: '基础数据' },
    { itemKey: 'Live', text: '测试功能' },
    { itemKey: 'Setting', text: '设置' },
];
const icons: Record<string, typeof IconHome> = { Home: IconHome, Histogram: IconHistogram, Live: IconLive, Setting: IconSetting };

export default function LayoutDemo() {
    return (
        <Layout style={{ border: '1px solid var(--semi-color-border)' }}>
            <Layout.Sider style={{ 'background-color': 'var(--semi-color-bg-1)' }}>
                <Nav
                    aria-label="侧边导航"
                    defaultSelectedKeys={['Home']}
                    style={{ 'max-width': '220px', height: '100%' }}
                    items={items}
                    renderItemIcon={({ item }) => <Dynamic component={icons[String(item.itemKey)]} size="large" />}
                    header={{
                        logo: () => <IconSemiLogo style={{ 'font-size': '36px' }} />,
                        text: 'Semi Design',
                    }}
                    footer={{
                        collapseButton: true,
                    }}
                />
            </Layout.Sider>
            <Layout>
                <Layout.Header style={{ 'background-color': 'var(--semi-color-bg-1)' }}>
                    <Nav
                        aria-label="顶部导航"
                        mode="horizontal"
                        footer={
                            <>
                                <Button theme="borderless" aria-label="通知" icon={<IconBell size="large" />} style={{ color: 'var(--semi-color-text-2)', 'margin-right': '12px' }} />
                                <Button theme="borderless" aria-label="帮助" icon={<IconHelpCircle size="large" />} style={{ color: 'var(--semi-color-text-2)', 'margin-right': '12px' }} />
                                <Avatar color="orange" size="small">
                                    YJ
                                </Avatar>
                            </>
                        }
                    />
                </Layout.Header>
                <Layout.Content style={{ padding: '24px', 'background-color': 'var(--semi-color-bg-0)' }}>
                    <Breadcrumb style={{ 'margin-bottom': '24px' }} routes={['首页', '当这个页面标题很长时需要省略', '上一页', '详情页']} />
                    <div style={{ 'border-radius': '10px', border: '1px solid var(--semi-color-border)', height: '376px', padding: '32px' }}>
                        <Skeleton placeholder={<Skeleton.Paragraph rows={2} />} loading>
                            <p>Hi, Bytedance dance dance.</p>
                            <p>Hi, Bytedance dance dance.</p>
                        </Skeleton>
                    </div>
                </Layout.Content>
                <Layout.Footer style={{ display: 'flex', 'justify-content': 'space-between', padding: '20px', color: 'var(--semi-color-text-2)', 'background-color': 'var(--semi-grey-0)' }}>
                    <span style={{ display: 'flex', 'align-items': 'center' }}>
                        <IconBytedanceLogo size="large" style={{ 'margin-right': '8px' }} />
                        <span>Copyright © 2019 ByteDance. All Rights Reserved. </span>
                    </span>
                    <span>
                        <span style={{ 'margin-right': '24px' }}>平台客服</span>
                        <span>反馈建议</span>
                    </span>
                </Layout.Footer>
            </Layout>
        </Layout>
    );
}

API Reference

Layout

Props

PropertyDescriptionTypeDefault
hasSiderIndicates that a Sider is inside; usually not needed. A Sider registers with the nearest Layout when it is created, so the has-sider class is present on the first frame, including SSR. Passing false cannot remove the has-sider class that a Sider adds.boolean-
prefixClsClass name prefix.string"semi-layout"
tagNameTag name of the root element.keyof HTMLElementTagNameMap"section"

class, style, ref and native attributes (including role and aria-*) fall through to the root element.

Layout.Header, Layout.Footer, Layout.Content

The three share the same API. Their default tags are header, footer and main.

Layout.Header

Props

PropertyDescriptionTypeDefault
prefixClsClass name prefix. Defaults to semi-layout.string-
tagNameTag name of the root element. Defaults to header.keyof HTMLElementTagNameMap-
typeFor internal use; decides the class name suffix. Defaults to header.string-

Props

PropertyDescriptionTypeDefault
prefixClsClass name prefix. Defaults to semi-layout.string-
tagNameTag name of the root element. Defaults to footer.keyof HTMLElementTagNameMap-
typeFor internal use; decides the class name suffix. Defaults to footer.string-

Layout.Content

Props

PropertyDescriptionTypeDefault
prefixClsClass name prefix. Defaults to semi-layout.string-
tagNameTag name of the root element. Defaults to main.keyof HTMLElementTagNameMap-
typeFor internal use; decides the class name suffix. Defaults to content.string-

class, style, ref and native attributes (including role and aria-*) fall through to each root element.

Layout.Sider

Props

PropertyDescriptionTypeDefault
breakpointBreakpoints that trigger the responsive layout: xs, sm, md, lg, xl or xxl. Read once at mount.TypeArray<keyof ResponsiveMap>-
onBreakpointCalled once at mount for each screen listed in breakpoint with the current viewport state, then whenever it starts or stops matching, with the screen name and whether it matches.Type(screen: keyof ResponsiveMap, match: boolean) => void-
prefixClsClass name prefix. Defaults to semi-layout.string-

class, style, ref and native attributes and events (including role, aria-* and onClick) fall through to the root aside element.

responsive map

{
  xs: '(max-width: 575px)',
  sm: '(min-width: 576px)',
  md: '(min-width: 768px)',
  lg: '(min-width: 992px)',
  xl: '(min-width: 1200px)',
  xxl: '(min-width: 1600px)',
}

Accessibility

ARIA

  • Sider renders an aside; pass aria-label to describe what this Sider is for.
  • Header and Footer render header and footer by default. Inside the section of a Layout they are not banner or contentinfo landmarks; pass role="banner" or role="contentinfo" when they are the header or footer of the whole page.
  • Content renders main by default, and a page should have only one main; when nesting, use tagName to switch to another tag.
  • Header, Content and Footer accept role and aria-label to describe the corresponding element.