ComponentsFeedback

Banner

A full-width strip for a state that covers everything below it: the whole app when you are offline, or a whole record when it is archived or must not be contacted. The largest of three message levels.

Playground

Change the controls to try each prop. The code under the preview updates as you go and lists only the props you changed; copy it into your page.

Hidden from lists and search. Its history stays.
States
import { Banner } from '@/ui/banner';<Banner title="">  Hidden from lists and search. Its history stays.</Banner>

Warning, app-wide

The offline strip sits above the sidebar and the page, centred, until the connection is back. Then it goes without a toast. OfflineBanner draws it from one prop, online, which the app shell takes from the browser's online and offline events; it is announced once as it appears. Press the button to drop and restore the connection. For any other app-wide strip, pass global for the 36px centred strip.

import { useState } from 'react';
import { OfflineBanner } from '@/ui/banner';
import { Button } from '@/ui/button';

// The app shell passes `online` from the browser's online and offline events. Press the button to drop the connection.
export default function BannerOffline() {
  const [online, setOnline] = useState(false);
  return (
    <div className="flex w-full flex-col items-start gap-200">
      <OfflineBanner online={online} />
      <Button size="sm" onClick={() => setOnline(!online)}>
        {online ? 'Go offline' : 'Back online'}
      </Button>
    </div>
  );
}

Neutral

A whole record is in a lasting state, such as archived. The strip sits directly under the record's header, edge to edge, on the current grey. A bold label first, one sentence after, one action at most.

ArchivedHidden from lists and search. Its history stays.
import { ArchiveIcon } from '@phosphor-icons/react';
import { Banner } from '@/ui/banner';
import { Button } from '@/ui/button';

export default function BannerArchived() {
  return (
    <Banner icon={<ArchiveIcon />} title="Archived" action={<Button size="sm">Restore</Button>}>
      Hidden from lists and search. Its history stays.
    </Banner>
  );
}

Error

A rule the whole record must follow, such as Do not contact.

Do not contactDaniel asked not to be contacted. Don’t email or call him about roles.
import { Banner } from '@/ui/banner';

export default function BannerDoNotContact() {
  return (
    <Banner appearance="error" title="Do not contact">
      Daniel asked not to be contacted. Don’t email or call him about roles.
    </Banner>
  );
}

Information

A fact about the whole record that the person should know before acting.

ImportedThis company came from your Bullhorn import. Check its owner before you contact it.
import { Banner } from '@/ui/banner';

export default function BannerInformation() {
  return (
    <Banner appearance="information" title="Imported">
      This company came from your Bullhorn import. Check its owner before you contact it.
    </Banner>
  );
}

Next step

The one thing waiting on a Job, at the top of the Job page, on the selected fill with a brand dot. When nothing waits, show no banner.

Next:3 applicants to review for Senior Data Engineer
import { Banner } from '@/ui/banner';
import { Button } from '@/ui/button';

export default function BannerNext() {
  return (
    <Banner appearance="next" title="Next:" action={<Button size="sm" variant="primary">Review applicants</Button>}>
      3 applicants to review for Senior Data Engineer
    </Banner>
  );
}

On this page