基础

布局

用于快捷划分页面整体布局

概述

  • Layout:布局容器,其下可嵌套 Header Sider Content Footer 或 Layout 本身,可以放在任何父容器中。
  • Header:顶部布局,其下可嵌套任何元素,只能放在 Layout 中。
  • Sider:侧边栏,其下可嵌套任何元素,只能放在 Layout 中。
  • Content:内容部分,其下可嵌套任何元素,只能放在 Layout 中。
  • Footer:底部布局,其下可嵌套任何元素,只能放在 Layout 中。

代码演示

如何引入

<script lang="ts">
    import { Layout, LayoutContent, LayoutFooter, LayoutHeader, LayoutSider } from '@fibrae/semi-ui-svelte';
</script>

子组件分别是 LayoutHeader、LayoutSider、LayoutContent、LayoutFooter,对应下文的 Layout.Header、Layout.Sider、Layout.Content、Layout.Footer,按名导入。

三行布局

Header
Content
Footer
代码
<script lang="ts">
    import { Layout, LayoutContent, LayoutFooter, LayoutHeader } from '@fibrae/semi-ui-svelte';

    const commonStyle = 'height: 64px; line-height: 64px; background: var(--semi-color-fill-0)';
</script>

<Layout class="components-layout-demo">
    <LayoutHeader style={commonStyle}>Header</LayoutHeader>
    <LayoutContent style="height: 300px; line-height: 300px">Content</LayoutContent>
    <LayoutFooter style={commonStyle}>Footer</LayoutFooter>
</Layout>

左侧边栏布局

Header
Content
Footer
代码
<script lang="ts">
    import { Layout, LayoutContent, LayoutFooter, LayoutHeader, LayoutSider } from '@fibrae/semi-ui-svelte';

    const commonStyle = 'height: 64px; line-height: 64px; background: var(--semi-color-fill-0)';
</script>

<Layout class="components-layout-demo">
    <LayoutHeader style={commonStyle}>Header</LayoutHeader>
    <Layout>
        <LayoutSider style="width: 120px; background: var(--semi-color-fill-2)">Sider</LayoutSider>
        <LayoutContent style="height: 300px; line-height: 300px">Content</LayoutContent>
    </Layout>
    <LayoutFooter style={commonStyle}>Footer</LayoutFooter>
</Layout>

右侧边栏布局

Header
Content
Footer
代码
<script lang="ts">
    import { Layout, LayoutContent, LayoutFooter, LayoutHeader, LayoutSider } from '@fibrae/semi-ui-svelte';

    const commonStyle = 'height: 64px; line-height: 64px; background: var(--semi-color-fill-0)';
</script>

<Layout class="components-layout-demo">
    <LayoutHeader style={commonStyle}>Header</LayoutHeader>
    <Layout>
        <LayoutContent style="height: 300px; line-height: 300px">Content</LayoutContent>
        <LayoutSider style="width: 120px; background: var(--semi-color-fill-2)">Sider</LayoutSider>
    </Layout>
    <LayoutFooter style={commonStyle}>Footer</LayoutFooter>
</Layout>

侧边栏布局

Header
Content
Footer
代码
<script lang="ts">
    import { Layout, LayoutContent, LayoutFooter, LayoutHeader, LayoutSider } from '@fibrae/semi-ui-svelte';

    const commonStyle = 'height: 64px; line-height: 64px; background: var(--semi-color-fill-0)';
</script>

<Layout class="components-layout-demo">
    <LayoutSider style="width: 120px; background: var(--semi-color-fill-2)">Sider</LayoutSider>
    <Layout>
        <LayoutHeader style={commonStyle}>Header</LayoutHeader>
        <LayoutContent style="height: 300px; line-height: 300px">Content</LayoutContent>
        <LayoutFooter style={commonStyle}>Footer</LayoutFooter>
    </Layout>
</Layout>

响应式布局

侧边栏预设了六个响应尺寸:xs、sm、md、lg、xl、xxl。可以通过设置 breakpoint 属性设置断点。

通过 onBreakpoint 调用回调函数。

Header
Content
Footer
代码
<script lang="ts">
    import { Layout, LayoutContent, LayoutFooter, LayoutHeader, LayoutSider } from '@fibrae/semi-ui-svelte';

    const commonStyle = 'height: 64px; line-height: 64px; background: var(--semi-color-fill-0)';
</script>

<Layout class="components-layout-demo">
    <LayoutHeader style={commonStyle}>Header</LayoutHeader>
    <Layout>
        <LayoutSider breakpoint={['md']} onBreakpoint={(screen, matches) => console.log(screen, matches)} style="width: 120px; background: var(--semi-color-fill-2)">Sider</LayoutSider>
        <LayoutContent style="height: 300px; line-height: 300px">Content</LayoutContent>
    </Layout>
    <LayoutFooter style={commonStyle}>Footer</LayoutFooter>
</Layout>

布局示例

顶部导航布局

Copyright © 2023 ByteDance. All Rights Reserved. 平台客服 反馈建议
代码
<script lang="ts">
    import { IconBell, IconBytedanceLogo, IconHelpCircle, IconHome, IconLive, IconSemiLogo, IconSetting } from '@fibrae/semi-icons-svelte';
    import { Avatar, Breadcrumb, Button, Layout, LayoutContent, LayoutFooter, LayoutHeader, Nav, NavFooter, NavHeader, NavItem, Skeleton, SkeletonParagraph } from '@fibrae/semi-ui-svelte';

    const routes = ['首页', '当这个页面标题很长时需要省略', '上一页', '详情页'];
    const actionStyle = 'color: var(--semi-color-text-2); margin-inline-end: 12px';
</script>

{#snippet header()}
    <NavHeader>
        <IconSemiLogo style="font-size: 36px" />
    </NavHeader>
{/snippet}

{#snippet footer()}
    <NavFooter>
        <Button aria-label="通知" style={actionStyle} theme="borderless">
            {#snippet icon()}
                <IconBell size="large" />
            {/snippet}
        </Button>
        <Button aria-label="帮助" style={actionStyle} theme="borderless">
            {#snippet icon()}
                <IconHelpCircle size="large" />
            {/snippet}
        </Button>
        <Avatar color="orange" size="small">YJ</Avatar>
    </NavFooter>
{/snippet}

<Layout style="border: 1px solid var(--semi-color-border)">
    <LayoutHeader style="background-color: var(--semi-color-bg-1)">
        <div>
            <Nav {footer} {header} mode="horizontal" selectedKeys={['Home']}>
                <NavItem itemKey="Home" text="首页">
                    {#snippet icon()}
                        <IconHome size="large" />
                    {/snippet}
                </NavItem>
                <NavItem itemKey="Live" text="直播">
                    {#snippet icon()}
                        <IconLive size="large" />
                    {/snippet}
                </NavItem>
                <NavItem itemKey="Setting" text="设置">
                    {#snippet icon()}
                        <IconSetting size="large" />
                    {/snippet}
                </NavItem>
            </Nav>
        </div>
    </LayoutHeader>
    <LayoutContent style="padding: 24px; background-color: var(--semi-color-bg-0)">
        <Breadcrumb {routes} style="margin-bottom: 24px" />
        <div style="border-radius: 10px; border: 1px solid var(--semi-color-border); height: 376px; padding: 32px">
            <Skeleton loading>
                {#snippet placeholder()}
                    <SkeletonParagraph rows={2} />
                {/snippet}
                <p>Hi, Bytedance dance dance.</p>
                <p>Hi, Bytedance dance dance.</p>
            </Skeleton>
        </div>
    </LayoutContent>
    <LayoutFooter 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-inline-end: 8px" />
            <span>Copyright © 2023 ByteDance. All Rights Reserved. </span>
        </span>
        <span>
            <span style="margin-inline-end: 24px">平台客服</span>
            <span>反馈建议</span>
        </span>
    </LayoutFooter>
</Layout>

顶部导航-侧边布局

Copyright © 2023 ByteDance. All Rights Reserved. 平台客服 反馈建议
代码
<script lang="ts">
    import { IconBell, IconBytedanceLogo, IconHelpCircle, IconHistogram, IconHome, IconLive, IconSemiLogo, IconSetting } from '@fibrae/semi-icons-svelte';
    import { Avatar, Breadcrumb, Button, Layout, LayoutContent, LayoutFooter, LayoutHeader, LayoutSider, Nav, NavFooter, NavHeader, Skeleton, SkeletonParagraph, type NavItems } from '@fibrae/semi-ui-svelte';

    const items: NavItems = [
        { itemKey: 'Home', text: '首页' },
        { itemKey: 'Histogram', text: '基础数据' },
        { itemKey: 'Live', text: '测试功能' },
        { itemKey: 'Setting', text: '设置' },
    ];
    const icons = { Home: IconHome, Histogram: IconHistogram, Live: IconLive, Setting: IconSetting };
    const routes = ['首页', '当这个页面标题很长时需要省略', '上一页', '详情页'];
    const actionStyle = 'color: var(--semi-color-text-2); margin-inline-end: 12px';
</script>

{#snippet topHeader()}
    <NavHeader>
        <IconSemiLogo style="height: 36px; font-size: 36px" />
    </NavHeader>
{/snippet}

{#snippet topFooter()}
    <NavFooter>
        <Button aria-label="通知" style={actionStyle} theme="borderless">
            {#snippet icon()}
                <IconBell size="large" />
            {/snippet}
        </Button>
        <Button aria-label="帮助" style={actionStyle} theme="borderless">
            {#snippet icon()}
                <IconHelpCircle size="large" />
            {/snippet}
        </Button>
        <Avatar color="orange" size="small">YJ</Avatar>
    </NavFooter>
{/snippet}

<Layout style="border: 1px solid var(--semi-color-border)">
    <LayoutHeader style="background-color: var(--semi-color-bg-1)">
        <div>
            <Nav aria-label="顶部导航" footer={topFooter} header={topHeader} mode="horizontal" selectedKeys={['Home']}>
                <!-- Nav renders its children straight into the list, so other content needs its own li -->
                <li style="display: contents">
                    <span style="color: var(--semi-color-text-2)">
                        <span style="margin-inline-end: 24px; color: var(--semi-color-text-0); font-weight: 600">模版推荐</span>
                        <span style="margin-inline-end: 24px">所有模版</span>
                        <span>我的模版</span>
                    </span>
                </li>
            </Nav>
        </div>
    </LayoutHeader>
    <Layout>
        <LayoutSider style="background-color: var(--semi-color-bg-1)">
            <Nav aria-label="侧边导航" footer={{ collapseButton: true }} {items} selectedKeys={['Home']} style="max-width: 220px; height: 100%">
                {#snippet icon({ item })}
                    {@const Icon = icons[item.itemKey as keyof typeof icons]}
                    <Icon size="large" />
                {/snippet}
            </Nav>
        </LayoutSider>
        <LayoutContent style="padding: 24px; background-color: var(--semi-color-bg-0)">
            <Breadcrumb {routes} style="margin-bottom: 24px" />
            <div style="border-radius: 10px; border: 1px solid var(--semi-color-border); height: 376px; padding: 32px">
                <Skeleton loading>
                    {#snippet placeholder()}
                        <SkeletonParagraph rows={2} />
                    {/snippet}
                    <p>Hi, Bytedance dance dance.</p>
                    <p>Hi, Bytedance dance dance.</p>
                </Skeleton>
            </div>
        </LayoutContent>
    </Layout>
    <LayoutFooter 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-inline-end: 8px" />
            <span>Copyright © 2023 ByteDance. All Rights Reserved. </span>
        </span>
        <span>
            <span style="margin-inline-end: 24px">平台客服</span>
            <span>反馈建议</span>
        </span>
    </LayoutFooter>
</Layout>

侧边导航

Copyright © 2019 ByteDance. All Rights Reserved. 平台客服 反馈建议
代码
<script lang="ts">
    import { IconBell, IconBytedanceLogo, IconHelpCircle, IconHistogram, IconHome, IconLive, IconSemiLogo, IconSetting } from '@fibrae/semi-icons-svelte';
    import { Avatar, Breadcrumb, Button, Layout, LayoutContent, LayoutFooter, LayoutHeader, LayoutSider, Nav, NavFooter, NavHeader, Skeleton, SkeletonParagraph, type NavItems } from '@fibrae/semi-ui-svelte';

    const items: NavItems = [
        { itemKey: 'Home', text: '首页' },
        { itemKey: 'Histogram', text: '基础数据' },
        { itemKey: 'Live', text: '测试功能' },
        { itemKey: 'Setting', text: '设置' },
    ];
    const icons = { Home: IconHome, Histogram: IconHistogram, Live: IconLive, Setting: IconSetting };
    const routes = ['首页', '当这个页面标题很长时需要省略', '上一页', '详情页'];
    const actionStyle = 'color: var(--semi-color-text-2); margin-inline-end: 12px';
</script>

{#snippet sideHeader()}
    <NavHeader text="Semi Design">
        {#snippet logo()}
            <IconSemiLogo style="font-size: 36px" />
        {/snippet}
    </NavHeader>
{/snippet}

{#snippet topFooter()}
    <NavFooter>
        <Button aria-label="通知" style={actionStyle} theme="borderless">
            {#snippet icon()}
                <IconBell size="large" />
            {/snippet}
        </Button>
        <Button aria-label="帮助" style={actionStyle} theme="borderless">
            {#snippet icon()}
                <IconHelpCircle size="large" />
            {/snippet}
        </Button>
        <Avatar color="orange" size="small">YJ</Avatar>
    </NavFooter>
{/snippet}

<Layout style="border: 1px solid var(--semi-color-border)">
    <LayoutSider style="background-color: var(--semi-color-bg-1)">
        <Nav aria-label="侧边导航" footer={{ collapseButton: true }} header={sideHeader} {items} selectedKeys={['Home']} style="max-width: 220px; height: 100%">
            {#snippet icon({ item })}
                {@const Icon = icons[item.itemKey as keyof typeof icons]}
                <Icon size="large" />
            {/snippet}
        </Nav>
    </LayoutSider>
    <Layout>
        <LayoutHeader style="background-color: var(--semi-color-bg-1)">
            <Nav aria-label="顶部导航" footer={topFooter} mode="horizontal" />
        </LayoutHeader>
        <LayoutContent style="padding: 24px; background-color: var(--semi-color-bg-0)">
            <Breadcrumb {routes} style="margin-bottom: 24px" />
            <div style="border-radius: 10px; border: 1px solid var(--semi-color-border); height: 376px; padding: 32px">
                <Skeleton loading>
                    {#snippet placeholder()}
                        <SkeletonParagraph rows={2} />
                    {/snippet}
                    <p>Hi, Bytedance dance dance.</p>
                    <p>Hi, Bytedance dance dance.</p>
                </Skeleton>
            </div>
        </LayoutContent>
        <LayoutFooter 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-inline-end: 8px" />
                <span>Copyright © 2019 ByteDance. All Rights Reserved. </span>
            </span>
            <span>
                <span style="margin-inline-end: 24px">平台客服</span>
                <span>反馈建议</span>
            </span>
        </LayoutFooter>
    </Layout>
</Layout>

API 参考

Layout

Props

属性说明类型默认值
childrenLayout 的内容,可放 LayoutHeader、LayoutSider、LayoutContent、LayoutFooter 或嵌套的 Layout。Snippet-
hasSider表示子元素里有 Sider。直接子节点中的 Sider 会自动识别;嵌套更深的 Sider 在挂载后才登记,服务端渲染时传 true 可避免样式闪动。传 false 不能去掉 Sider 带来的 has-sider 类。boolean-
prefixCls类名前缀。string"semi-layout"
tagName根元素的标签名。keyof HTMLElementTagNameMap"section"

class、style 与原生属性(含 role、aria-*)落在根元素上。

Layout.Header、Layout.Footer、Layout.Content

三者的 API 相同,默认标签依次为 header、footer、main。

Layout.Header

Props

属性说明类型默认值
children顶部区域的内容。Snippet-
prefixCls类名前缀。string"semi-layout"
tagName根元素的标签名。keyof HTMLElementTagNameMap"header"
type内部使用,决定类名后缀。string"header"

Props

属性说明类型默认值
children底部区域的内容。Snippet-
prefixCls类名前缀。string"semi-layout"
tagName根元素的标签名。keyof HTMLElementTagNameMap"footer"
type内部使用,决定类名后缀。string"footer"

Layout.Content

Props

属性说明类型默认值
children内容区域的内容。Snippet-
prefixCls类名前缀。string"semi-layout"
tagName根元素的标签名。keyof HTMLElementTagNameMap"main"
type内部使用,决定类名后缀。string"content"

class、style 与原生属性(含 role、aria-*)落在各自的根元素上。

Layout.Sider

Props

属性说明类型默认值
breakpoint触发响应式布局的断点,可选 xs、sm、md、lg、xl、xxl。仅在挂载时读取一次。TypeArray<keyof ResponsiveMap>-
children侧边栏的内容。Snippet-
onBreakpoint挂载时对 breakpoint 中的每个断点按当前视口调用一次,之后在命中或离开时调用,参数为断点名与是否命中。Type(screen: keyof ResponsiveMap, match: boolean) => void-
prefixCls类名前缀。string"semi-layout"

class、style 与原生属性和事件(含 role、aria-*、onclick)落在根元素 aside 上。

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 渲染 aside,可传入 aria-label 描述该 Sider 的作用。
  • Header、Footer 默认渲染 header、footer,位于 Layout 的 section 内时不会成为 banner、contentinfo 地标;作为整页的页头、页脚时传入 role="banner"、role="contentinfo"。
  • Content 默认渲染 main,一个页面只应有一个 main;嵌套使用时可通过 tagName 换成其他标签。
  • Header、Content、Footer 可传入 role、aria-label 描述对应元素的作用。