Design Collaboration · Dark Mode

Dark Mode

Every Semi color variable has a light and a dark value, so a page can switch between light and dark mode.

Dark mode

🤩 Every Semi color variable has a light and a dark value, which can be easily switched.
🌒 Semi also supports the use of dark mode in a partial area of the page.

Semi will automatically mount the global color palette on the root element (:root). We have built in some commonly used CSS Tokens. For detailed Token details, please refer to Design Variables We recommend that you configure color and background-color on the body, your business components can automatically inherit the default background color and text color from the body, and adaptive light/dark color switching

// css
body {
    color: var(--semi-color-text-0);
    background-color: var(--semi-color-bg-0);
}

How to switch

To use Dark Mode, you could simply add [theme-mode='dark'] to body in any way you prefer. The attribute works on any ancestor element. The variables are declared on :root and declared again on the element carrying theme-mode='dark', so read them on body or a descendant to get the dark values. Here is a quick idea:

const body = document.body;
if (body.hasAttribute('theme-mode')) {
    body.removeAttribute('theme-mode');
} else {
    body.setAttribute('theme-mode', 'dark');
}

For instance:

Code
<script lang="ts">
    import { Button } from '@fibrae/semi-ui-svelte';

    const switchMode = () => {
        const body = document.body;
        if (body.hasAttribute('theme-mode')) {
            body.removeAttribute('theme-mode');
        } else {
            body.setAttribute('theme-mode', 'dark');
        }
    };
</script>

<Button onclick={switchMode}>Switch Mode</Button>

Customize the colors of both modes

Override the --semi-* color variables in your own stylesheet: light values on :root, dark values on [theme-mode='dark']. Semi's styles sit in @layer semi, so your unlayered rules always win without !important:

:root {
    --semi-color-primary: var(--semi-violet-5);
}

[theme-mode='dark'] {
    --semi-color-primary: var(--semi-violet-4);
}

With partial light/dark mode, the .semi-always-light and .semi-always-dark elements declare the color variables again; to make an override reach those areas too, add the two classes to the selectors above.

Keep Consistency with System Theme

If you want the mode of the site to change with the system setting, you may find this property Prefers-color-scheme helpful. To change system setting in macOS, go to System Preferences -> General -> Appearance

You could add a listener for this property using js. Here is another example:

const mql = window.matchMedia('(prefers-color-scheme: dark)');

function matchMode(e) {
    const body = document.body;
    if (e.matches) {
        if (!body.hasAttribute('theme-mode')) {
            body.setAttribute('theme-mode', 'dark');
        }
    } else {
        if (body.hasAttribute('theme-mode')) {
            body.removeAttribute('theme-mode');
        }
    }
}

matchMode(mql);
mql.addEventListener('change', matchMode);

Block Dark/Light Mode

Semi 2.0 natively supports block dark/bright color mode, and you can add .semi-always-dark or .semi-always-light to areas that require block dark or bright colors as needed.

Note: pop-up layers render in body, outside the element carrying .semi-always-dark or .semi-always-light. When the pop-up layer of Tooltip, Popover, Dropdown, or the dropdown of Select and similar components opens, it takes the class of the innermost .semi-always-dark or .semi-always-light element around its trigger, so it uses the same mode as the area of the trigger. Modal, SideSheet, Toast and Notification do not follow a trigger; they render in body by default and use the page theme.




Finished
This is a description.
2
In Progress
This is a description.
3
Waiting
This is a description.

Finished
This is a description
In Progress
This is a description
3
Waiting
This is a description
XZ 5
YB 5
LX 5
YZ
HW
XM

grey tag
blue tag
ghost tag
solid tag
red tag
green tag
orange tag
teal tag
violet tag
white tag

I am Popover
I am Tooltip

  • 审核中
    2019-07-14 10:35
  • 发布成功
    2019-06-13 16:17
  • 审核失败
    2019-05-14 18:34
Copyright © 2019 ByteDance. All Rights Reserved. 平台客服 反馈建议
Code
<script lang="ts">
    import { IconBell, IconBytedanceLogo, IconCamera, IconEdit, IconHelpCircle, IconHistogram, IconHome, IconList, IconLive, IconSemiLogo, IconSetting } from '@fibrae/semi-icons-svelte';
    import { Avatar, Badge, Breadcrumb, Button, Layout, LayoutContent, LayoutFooter, LayoutHeader, LayoutSider, Nav, NavFooter, NavHeader, Pagination, Popover, Rating, Row, Steps, Tag, Timeline, Tooltip, type NavItems, type StepData, type TimelineData } from '@fibrae/semi-ui-svelte';

    let mode = $state('semi-always-dark');

    const switchMode = () => {
        mode = mode === 'semi-always-dark' ? 'semi-always-light' : 'semi-always-dark';
    };

    const rowStyle = 'margin: 16px 10px';
    const badgeStyle = 'width: 42px; height: 42px; border-radius: 4px; display: inline-block';
    const tagStyle = 'margin-inline-end: 8px; margin-bottom: 8px';
    const headerButtonStyle = 'color: var(--semi-color-text-2); margin-inline-end: 12px';

    const sideItems: NavItems = [
        { itemKey: 'Home', text: '首页' },
        { itemKey: 'Histogram', text: '基础数据' },
        { itemKey: 'Live', text: '测试功能' },
        { itemKey: 'Setting', text: '设置' },
    ];
    const sideIcons = { Home: IconHome, Histogram: IconHistogram, Live: IconLive, Setting: IconSetting };

    const exampleItems: NavItems = [
        { itemKey: 'user', text: 'Option1' },
        { itemKey: 'union', text: 'Option2' },
        { itemKey: 'approve-management', text: 'Group3', items: ['3-1', '3-2'] },
    ];
    const exampleIcons = { user: IconEdit, union: IconCamera, 'approve-management': IconList };

    const routes = ['首页', '当这个页面标题很长时需要省略', '上一页', '详情页'];
    const tags = [
        { color: 'grey', label: 'grey tag' },
        { color: 'blue', label: 'blue tag' },
        { color: 'blue', type: 'ghost', label: 'ghost tag' },
        { color: 'blue', type: 'solid', label: 'solid tag' },
        { color: 'red', label: 'red tag' },
        { color: 'green', label: 'green tag' },
        { color: 'orange', label: 'orange tag' },
        { color: 'teal', label: 'teal tag' },
        { color: 'violet', label: 'violet tag' },
        { color: 'white', label: 'white tag' },
    ] as const;

    const steps = (description: string): StepData[] => [
        { title: 'Finished', description },
        { title: 'In Progress', description },
        { title: 'Waiting', description },
    ];
    const timeline: TimelineData[] = [
        { time: '2019-07-14 10:35', content: '审核中', type: 'ongoing' },
        { time: '2019-06-13 16:17', content: '发布成功', type: 'success' },
        { time: '2019-05-14 18:34', content: '审核失败', type: 'error' },
    ];
</script>

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

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

{#snippet tipContent()}hi semi-design{/snippet}

<Button style="margin-bottom: 4px" onclick={switchMode}>Switch Content Mode</Button>
<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={sideItems} selectedKeys={['Home']} style="max-width: 220px; height: 100%">
                {#snippet icon({ item })}
                    {@const Icon = sideIcons[item.itemKey as keyof typeof sideIcons]}
                    <Icon size="large" />
                {/snippet}
            </Nav>
        </LayoutSider>
        <LayoutContent class={mode} style="padding: 24px; background-color: var(--semi-color-bg-1)">
            <Breadcrumb {routes} style="margin-bottom: 24px" />
            <div style="border-radius: 10px; border: 1px solid var(--semi-color-border); height: 700px; padding: 32px">
                <Row style={rowStyle}>
                    <Nav aria-label="示例导航" items={exampleItems} mode="horizontal">
                        {#snippet icon({ item })}
                            {@const Icon = exampleIcons[item.itemKey as keyof typeof exampleIcons]}
                            <Icon />
                        {/snippet}
                    </Nav>
                    <br />
                    <br />
                    <Pagination showSizeChanger total={80} />
                    <br />
                    <Steps current={1} dataSource={steps('This is a description.')} />
                    <br />
                    <Steps current={1} dataSource={steps('This is a description')} status="error" />
                </Row>
                <Row style={rowStyle}>
                    <div style="display: flex">
                        <div style="padding: 8px">
                            <Badge count={5} theme="solid">
                                <Avatar color="blue" shape="square" style={badgeStyle}>XZ</Avatar>
                            </Badge>
                        </div>
                        <div style="padding: 8px">
                            <Badge count={5} theme="light">
                                <Avatar color="cyan" shape="square" style={badgeStyle}>YB</Avatar>
                            </Badge>
                        </div>
                        <div style="padding: 8px">
                            <Badge count={5} theme="inverted">
                                <Avatar color="indigo" shape="square" style={badgeStyle}>LX</Avatar>
                            </Badge>
                        </div>
                        <div style="padding: 8px">
                            <Badge dot theme="solid">
                                <Avatar color="light-blue" shape="square" style={badgeStyle}>YZ</Avatar>
                            </Badge>
                        </div>
                        <div style="padding: 8px">
                            <Badge dot theme="light">
                                <Avatar color="teal" shape="square" style={badgeStyle}>HW</Avatar>
                            </Badge>
                        </div>
                        <div style="padding: 8px; border-radius: 4px; background-color: var(--semi-color-fill-0)">
                            <Badge dot theme="inverted">
                                <Avatar color="green" shape="square" style={badgeStyle}>XM</Avatar>
                            </Badge>
                        </div>
                    </div>
                    <br />
                    <div>
                        {#each tags as tag (tag.label)}
                            <Tag color={tag.color} style={tagStyle} type={'type' in tag ? tag.type : undefined}>
                                {tag.label}
                            </Tag>
                        {/each}
                    </div>
                    <br />
                    <div style="display: flex; align-items: center">
                        <Popover content={tipContent} style="padding: 8px">
                            <Tag style="margin-inline-end: 8px">I am Popover</Tag>
                        </Popover>
                        <Tooltip content={tipContent}>
                            <Tag style="margin-inline-end: 8px">I am Tooltip</Tag>
                        </Tooltip>
                        <Rating size="small" style="margin-inline-end: 8px" value={3} />
                    </div>
                    <br />
                    <Timeline dataSource={timeline} />
                </Row>
            </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 © 2019 ByteDance. All Rights Reserved. </span>
        </span>
        <span>
            <span style="margin-inline-end: 24px">平台客服</span>
            <span>反馈建议</span>
        </span>
    </LayoutFooter>
</Layout>