Skip to content
BigBang

Popular

    ↑↓Navigate↵OpenEscClose
    TR

    Templates

    Onboarding checklist

    Setup steps on the home page; progress, the next step open, done steps checked, a success state at the end and hiding with undo.

    The onboarding checklist shows the first setup steps of a new workspace in order. Only the next step is open and it carries one action; finished steps are checked. The list can always be hidden, and a toast undoes the hiding.

    Press the button of the next step: the step is done, the progress bar fills and the following step opens. After the last step, the card turns into a success state. The brand and mode switches in the page header restyle the preview too.

    Live example, StorybookOpen in Storybook
    Live example, StorybookOpen in Storybook
    Live example, StorybookOpen in Storybook
    Live example, StorybookOpen in Storybook
    Terminal window
    pnpm add @bigbang/ui
    Data Type Note
    Steps { title, text, action }[] Fixed order; each step has its own screen and counts as done on return.
    completed number Number of done steps from the server.
    Hidden boolean Stored per person; a hidden list does not come back on its own.
    import { Button, Card, Heading, Icon, Inline, ProgressBar, ResultState, Stack, Text, VisuallyHidden, motion, useToast } from '@bigbang/ui';
    import { useState } from 'react';
    import type { Lang } from '../../patterns/lang';
    import { AppShellTemplate } from '../../templates/app-shell/AppShellTemplate';
    const copy = {
    tr: {
    page: 'Ana sayfa',
    pageDescription: 'Çalışma alanınızı birkaç adımda hazırlayın.',
    title: 'Başlarken',
    progress: 'Kurulum ilerlemesi',
    progressText: (done: number, total: number) => `${total} adımdan ${done} tamamlandı`,
    done: 'Tamamlandı',
    current: 'Sıradaki adım',
    hide: 'Listeyi gizle',
    hidden: 'Başlarken listesi gizlendi.',
    show: 'Geri getir',
    steps: [
    { title: 'Profilinizi tamamlayın', text: 'Adınızı ve fotoğrafınızı ekleyin; ekip sizi tanısın.', action: 'Profili aç' },
    { title: 'Ekip arkadaşı davet edin', text: 'E-posta adresiyle davet gönderin; rolünü sonra değiştirebilirsiniz.', action: 'Davet gönder' },
    { title: 'İlk projeyi oluşturun', text: 'Proje, kayıtları ve görevleri bir arada tutar.', action: 'Proje oluştur' },
    { title: 'Bildirim tercihlerini seçin', text: 'Hangi olaylarda e-posta alacağınızı belirleyin.', action: 'Tercihleri aç' },
    ],
    completedTitle: 'Çalışma alanınız hazır',
    completedText: 'Tüm adımları tamamladınız. Bu kartı artık kapatabilirsiniz.',
    close: 'Kartı kapat',
    },
    en: {
    page: 'Home',
    pageDescription: 'Get your workspace ready in a few steps.',
    title: 'Getting started',
    progress: 'Setup progress',
    progressText: (done: number, total: number) => `${done} of ${total} steps done`,
    done: 'Done',
    current: 'Next step',
    hide: 'Hide the list',
    hidden: 'Getting started list hidden.',
    show: 'Bring back',
    steps: [
    { title: 'Complete your profile', text: 'Add your name and photo so the team knows you.', action: 'Open profile' },
    { title: 'Invite a teammate', text: 'Send an invitation by email; you can change their role later.', action: 'Send invitation' },
    { title: 'Create the first project', text: 'A project keeps records and tasks together.', action: 'Create project' },
    { title: 'Choose notification preferences', text: 'Decide which events send you an email.', action: 'Open preferences' },
    ],
    completedTitle: 'Your workspace is ready',
    completedText: 'You have done every step. You can close this card now.',
    close: 'Close the card',
    },
    };
    export type OnboardingChecklistBlockProps = {
    lang?: Lang;
    /** How many steps are already done on first render. */
    completed?: number;
    };
    /**
    * A getting-started checklist on the home page: progress, the next step open with its action,
    * done steps checked, a success state at the end and a hide action with undo.
    */
    export function OnboardingChecklistBlock({ lang = 'tr', completed = 1 }: OnboardingChecklistBlockProps) {
    const t = copy[lang];
    return (
    <AppShellTemplate lang={lang} current="home" title={t.page} description={t.pageDescription} width="md">
    <Checklist lang={lang} completed={completed} />
    </AppShellTemplate>
    );
    }
    function Checklist({ lang, completed }: { lang: Lang; completed: number }) {
    const t = copy[lang];
    const toast = useToast();
    const total = t.steps.length;
    const [done, setDone] = useState(() => Math.min(completed, total));
    const [pending, setPending] = useState(false);
    const [visible, setVisible] = useState(true);
    const titleId = `onboarding-${lang}`;
    // The pause stands in for the step's own screen; the demo marks it done on return.
    const complete = () => {
    setPending(true);
    setTimeout(() => {
    setPending(false);
    setDone((value) => Math.min(value + 1, total));
    }, 600);
    };
    const hide = () => {
    setVisible(false);
    toast.show({ title: t.hidden, action: { label: t.show, onClick: () => setVisible(true) } });
    };
    if (!visible) return null;
    return (
    <Card variant="outlined" padding="lg" className={motion.reveal}>
    <section {...(done === total ? { 'aria-label': t.title } : { 'aria-labelledby': titleId })}>
    {done === total ? (
    <ResultState
    tone="success"
    headingLevel={2}
    // Focus moves to the result only when the last step was just finished here.
    focusOnMount={completed < total}
    title={t.completedTitle}
    description={t.completedText}
    actions={
    <Button variant="secondary" onClick={hide}>
    {t.close}
    </Button>
    }
    />
    ) : (
    <Stack gap="lg">
    <Inline justify="between" align="center" gap="md" wrap>
    <Heading level={2} size="md" id={titleId}>
    {t.title}
    </Heading>
    <Button variant="tertiary" size="sm" onClick={hide}>
    {t.hide}
    </Button>
    </Inline>
    <ProgressBar label={t.progress} value={done} max={total} valueText={t.progressText(done, total)} description={t.progressText(done, total)} showValue={false} size="sm" />
    <ol className="flex flex-col gap-xs">
    {t.steps.map((step, index) => {
    const state = index < done ? 'done' : index === done ? 'current' : 'todo';
    return (
    <li
    key={step.title}
    aria-current={state === 'current' ? 'step' : undefined}
    className={['flex gap-md rounded-md p-md', state === 'current' ? 'bg-surface-sunken' : ''].join(' ')}
    >
    <span className="mt-xxs flex size-6 shrink-0 items-center justify-center" aria-hidden="true">
    {state === 'done' ? (
    <Icon name="circle-check" tone="success" className={motion.success} />
    ) : (
    <span className={['flex size-5 items-center justify-center rounded-full border type-label-sm', state === 'current' ? 'border-line-selected text-fg-default' : 'border-line-default text-fg-subtle'].join(' ')}>
    {index + 1}
    </span>
    )}
    </span>
    <Stack gap="xs" className="min-w-0 flex-1">
    <Text weight="medium" tone={state === 'done' ? 'subtle' : 'default'} {...(state === 'done' ? { className: 'line-through' } : {})}>
    {state === 'done' ? <VisuallyHidden>{`${t.done}:`}</VisuallyHidden> : null}
    {state === 'done' ? ' ' : null}
    {step.title}
    </Text>
    {state === 'current' ? (
    <div className={motion.collapsible}>
    <Stack gap="sm">
    <Text size="sm" tone="subtle">
    {step.text}
    </Text>
    <div>
    <Button size="sm" loading={pending} onClick={complete} endIcon={<Icon name="arrow-right" />}>
    {step.action}
    </Button>
    </div>
    </Stack>
    </div>
    ) : null}
    </Stack>
    </li>
    );
    })}
    </ol>
    </Stack>
    )}
    </section>
    </Card>
    );
    }
    1. AppShell template (width="md"), h1 “Home”.
    2. A Card with the h2 “Getting started” and “Hide the list”.
    3. An sm ProgressBar: “1 of 4 steps done”.
    4. An ordered list (ol): done steps with a check icon and a struck-through title; the next step open with its text and action button; the rest only a number and title.
    5. When every step is done, the card becomes a ResultState with an h2.
    6. Hiding and bringing back through a Toast.

    The card enters with motion.reveal. The next step’s content opens with motion.collapsible; a done step’s check grows in with motion.success; the progress bar fills with motion.progress. With reduced motion, scaling and sliding stop.

    • The list is a real ol; the next step carries aria-current="step".
    • Done steps are read with a hidden “Done:” prefix; strike-through and the green icon never carry the meaning alone.
    • The progress bar value is read as “1 of 4 steps done”, not as a percentage.
    • When the last step is finished here, focus moves to the success heading so the result is read at once; a page that opens with a finished list does not move focus.
    • F6 reaches the “Bring back” button in the notification region.

    Card, ProgressBar, ResultState, Button, Icon, Toast, VisuallyHidden. Patterns: Empty states, Notifications.