Monet
@etched/monet 0.0.1

Patterns

Whole screens built only from Monet's entries. Copy the code, then change the data and the actions.

Record list

A page of records: the header with the page's one primary action, a filter bar, a table with check boxes and a menu on each row, the pages, and a bar for the selected rows. The toolbar is one Tab stop; arrow keys move inside it. Archive can be undone, so it is not red; a delete is red and asks first, in an alert dialog.

Candidates

Everyone in the pipeline for an open role.

NameStageOwner
Ana RuizInterviewingSam Lee
José NúñezScheduledKai Ito
Lee ParkHiredSam Lee
Mira OseiOfferAna Ruiz
Noor HaddadInterviewingKai Ito
1–5 of 8
import { ArchiveIcon, ArrowRightIcon, CaretDownIcon, CopyIcon, DotsThreeIcon, FunnelSimpleIcon, MagnifyingGlassIcon, PencilSimpleIcon, PlusIcon, TrayIcon } from "@phosphor-icons/react";
import { useState } from "react";
import { ActionBar } from "@etched/monet/action-bar";
import { Button } from "@etched/monet/button";
import { Checkbox } from "@etched/monet/checkbox";
import { DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger } from "@etched/monet/dropdown";
import { Empty } from "@etched/monet/empty";
import { LayerCard } from "@etched/monet/layer-card";
import { PageHeader } from "@etched/monet/page-header";
import { Pagination } from "@etched/monet/pagination";
import { Pill, type PillTone } from "@etched/monet/pill";
import { Table } from "@etched/monet/table";
import { Toolbar, ToolbarButton, ToolbarInput, ToolbarSeparator } from "@etched/monet/toolbar";

type Stage = "Scheduled" | "Interviewing" | "Offer" | "Hired";

interface Candidate {
  id: string;
  name: string;
  role: string;
  stage: Stage;
  owner: string;
  email: string;
  applied: string;
}

const candidates: Candidate[] = [
  { id: "ana", name: "Ana Ruiz", role: "ASIC design engineer", stage: "Interviewing", owner: "Sam Lee", email: "ana.ruiz@example.com", applied: "Oct 2" },
  { id: "jose", name: "José Núñez", role: "Firmware engineer", stage: "Scheduled", owner: "Kai Ito", email: "jose.nunez@example.com", applied: "Oct 4" },
  { id: "lee", name: "Lee Park", role: "Thermal engineer", stage: "Hired", owner: "Sam Lee", email: "lee.park@example.com", applied: "Sep 12" },
  { id: "mira", name: "Mira Osei", role: "Physical design lead", stage: "Offer", owner: "Ana Ruiz", email: "mira.osei@example.com", applied: "Sep 20" },
  { id: "noor", name: "Noor Haddad", role: "Verification engineer", stage: "Interviewing", owner: "Kai Ito", email: "noor.haddad@example.com", applied: "Oct 6" },
  { id: "ivan", name: "Ivan Petrov", role: "DFT engineer", stage: "Scheduled", owner: "Sam Lee", email: "ivan.petrov@example.com", applied: "Oct 9" },
  { id: "chen", name: "Chen Wei", role: "Packaging engineer", stage: "Offer", owner: "Ana Ruiz", email: "chen.wei@example.com", applied: "Sep 28" },
  { id: "sara", name: "Sara Lind", role: "Power engineer", stage: "Interviewing", owner: "Kai Ito", email: "sara.lind@example.com", applied: "Oct 11" },
];

const tones: Record<Stage, PillTone> = { Scheduled: "purple", Interviewing: "cyan", Offer: "olive", Hired: "green" };

const stages: Stage[] = ["Scheduled", "Interviewing", "Offer", "Hired"];

const perPage = 5;

export function RecordList() {
  const [query, setQuery] = useState("");
  const [shown, setShown] = useState<Stage[]>([]);
  const [picked, setPicked] = useState(() => new Set<string>());
  const [page, setPage] = useState(1);

  const found = candidates.filter((person) => person.name.toLowerCase().includes(query.trim().toLowerCase()) && (!shown.length || shown.includes(person.stage)));
  const rows = found.slice((page - 1) * perPage, page * perPage);
  const all = rows.length > 0 && rows.every((person) => picked.has(person.id));

  const pick = (ids: string[], on: boolean) =>
    setPicked((current) => {
      const next = new Set(current);

      for (const id of ids) {
        if (on) next.add(id);
        else next.delete(id);
      }

      return next;
    });

  // A new filter shows its first page.
  const filter = (stage: Stage, on: boolean) => {
    setShown((current) => (on ? [...current, stage] : current.filter((s) => s !== stage)));
    setPage(1);
  };

  const clear = () => {
    setQuery("");
    setShown([]);
    setPage(1);
  };

  return (
    <div style={{ display: "grid", gap: "var(--et-space-4)" }}>
      <PageHeader
        title="Candidates"
        description="Everyone in the pipeline for an open role."
        actions={
          <Button variant="primary" icon={<PlusIcon />}>
            Add candidate
          </Button>
        }
      />
      <Toolbar aria-label="Filter candidates">
        <ToolbarInput
          icon={<MagnifyingGlassIcon />}
          aria-label="Search candidates"
          placeholder="Search candidates"
          value={query}
          onChange={(event) => {
            setQuery(event.target.value);
            setPage(1);
          }}
        />
        <DropdownMenu>
          <DropdownMenuTrigger
            render={
              <ToolbarButton icon={<FunnelSimpleIcon />}>
                {shown.length ? `Stage: ${shown.length}` : "Stage"}
                <CaretDownIcon />
              </ToolbarButton>
            }
          />
          <DropdownMenuContent align="start">
            {stages.map((stage) => (
              <DropdownMenuCheckboxItem key={stage} checked={shown.includes(stage)} onCheckedChange={(on) => filter(stage, on)}>
                {stage}
              </DropdownMenuCheckboxItem>
            ))}
          </DropdownMenuContent>
        </DropdownMenu>
        <ToolbarSeparator />
        <ToolbarButton disabled={!query && !shown.length} onClick={clear}>
          Clear
        </ToolbarButton>
      </Toolbar>
      {/* On a narrow screen, the table scrolls sideways inside its card. */}
      <LayerCard size="none" style={{ overflowX: "auto" }}>
        <Table aria-label="Candidates" style={{ minInlineSize: "36rem" }}>
          <thead>
            <tr>
              <th style={{ inlineSize: "2.75rem" }}>
                <Checkbox
                  aria-label="Select the candidates on this page"
                  checked={all}
                  indeterminate={!all && rows.some((person) => picked.has(person.id))}
                  disabled={!rows.length}
                  onCheckedChange={(on) => pick(rows.map((person) => person.id), on)}
                />
              </th>
              <th>Name</th>
              <th style={{ inlineSize: "9rem" }}>Stage</th>
              <th style={{ inlineSize: "8rem" }}>Owner</th>
              <th style={{ inlineSize: "3.75rem" }} aria-label="Actions" />
            </tr>
          </thead>
          <tbody>
            {rows.map((person) => (
              <tr key={person.id}>
                <td>
                  <Checkbox aria-label={`Select ${person.name}`} checked={picked.has(person.id)} onCheckedChange={(on) => pick([person.id], on)} />
                </td>
                <th scope="row">{person.name}</th>
                <td>
                  <Pill tone={tones[person.stage]}>{person.stage}</Pill>
                </td>
                <td>{person.owner}</td>
                <td data-align="end">
                  <DropdownMenu>
                    <DropdownMenuTrigger
                      render={<Button variant="ghost" size="sm" shape="square" aria-label={`Actions for ${person.name}`} icon={<DotsThreeIcon />} />}
                    />
                    <DropdownMenuContent align="end">
                      <DropdownMenuItem icon={<PencilSimpleIcon />}>Edit</DropdownMenuItem>
                      <DropdownMenuItem icon={<CopyIcon />}>Copy link</DropdownMenuItem>
                      <DropdownMenuSeparator />
                      <DropdownMenuItem icon={<ArchiveIcon />}>Archive</DropdownMenuItem>
                    </DropdownMenuContent>
                  </DropdownMenu>
                </td>
              </tr>
            ))}
            {!rows.length && (
              <tr>
                <td colSpan={5}>
                  <Empty size="sm" appearance="plain" icon={<TrayIcon />} title="No candidates match these filters" contents={<Button size="sm" onClick={clear}>Clear filters</Button>} />
                </td>
              </tr>
            )}
          </tbody>
        </Table>
      </LayerCard>
      <Pagination aria-label="Candidate pages" page={page} onPageChange={setPage} perPage={perPage} totalCount={found.length} />
      {picked.size > 0 && (
        <ActionBar
          aria-label="Selected candidates"
          count={`${picked.size} selected`}
          onDismiss={() => setPicked(new Set())}
          closeLabel="Clear selection"
          style={{ position: "sticky", insetBlockEnd: "var(--et-space-4)" }}
        >
          <Button size="sm" icon={<ArrowRightIcon />}>
            Move to stage
          </Button>
          <Button size="sm" icon={<ArchiveIcon />}>
            Archive
          </Button>
        </ActionBar>
      )}
    </div>
  );
}

Record details in a sheet

A row opens its record beside the list: click it, or focus it and press Enter. One sheet serves every row: it stays mounted and opens with open. Tabs split the record, and the actions stay at the bottom. Escape, the close button or Done closes the sheet, and focus goes back to the row.

NameRoleStage
Ana RuizASIC design engineerInterviewing
José NúñezFirmware engineerScheduled
Lee ParkThermal engineerHired
import { PencilSimpleIcon } from "@phosphor-icons/react";
import { useState } from "react";
import { Button } from "@etched/monet/button";
import { DescriptionList } from "@etched/monet/description-list";
import { LayerCard } from "@etched/monet/layer-card";
import { Pill, type PillTone } from "@etched/monet/pill";
import { Sheet, SheetActions, SheetBody, SheetClose, SheetDescription, SheetHeader, SheetRoot } from "@etched/monet/sheet";
import { Table, TableRow } from "@etched/monet/table";
import { Tabs, TabsPanel } from "@etched/monet/tabs";
import { Text } from "@etched/monet/text";

type Stage = "Scheduled" | "Interviewing" | "Offer" | "Hired";

interface Candidate {
  id: string;
  name: string;
  role: string;
  stage: Stage;
  owner: string;
  email: string;
  applied: string;
}

const candidates: Candidate[] = [
  { id: "ana", name: "Ana Ruiz", role: "ASIC design engineer", stage: "Interviewing", owner: "Sam Lee", email: "ana.ruiz@example.com", applied: "Oct 2" },
  { id: "jose", name: "José Núñez", role: "Firmware engineer", stage: "Scheduled", owner: "Kai Ito", email: "jose.nunez@example.com", applied: "Oct 4" },
  { id: "lee", name: "Lee Park", role: "Thermal engineer", stage: "Hired", owner: "Sam Lee", email: "lee.park@example.com", applied: "Sep 12" },
  { id: "mira", name: "Mira Osei", role: "Physical design lead", stage: "Offer", owner: "Ana Ruiz", email: "mira.osei@example.com", applied: "Sep 20" },
  { id: "noor", name: "Noor Haddad", role: "Verification engineer", stage: "Interviewing", owner: "Kai Ito", email: "noor.haddad@example.com", applied: "Oct 6" },
  { id: "ivan", name: "Ivan Petrov", role: "DFT engineer", stage: "Scheduled", owner: "Sam Lee", email: "ivan.petrov@example.com", applied: "Oct 9" },
  { id: "chen", name: "Chen Wei", role: "Packaging engineer", stage: "Offer", owner: "Ana Ruiz", email: "chen.wei@example.com", applied: "Sep 28" },
  { id: "sara", name: "Sara Lind", role: "Power engineer", stage: "Interviewing", owner: "Kai Ito", email: "sara.lind@example.com", applied: "Oct 11" },
];

const tones: Record<Stage, PillTone> = { Scheduled: "purple", Interviewing: "cyan", Offer: "olive", Hired: "green" };

const notes = [
  ["Sam Lee, Oct 14", "Strong on clock trees; led the clock rework on the last tape-out."],
  ["Kai Ito, Oct 15", "Phone screen went well. Asked good questions about the test floor."],
];

export function RecordDetails() {
  const [open, setOpen] = useState(false);
  const [person, setPerson] = useState<Candidate>();

  return (
    <>
      <LayerCard size="none" style={{ overflowX: "auto" }}>
        <Table aria-label="Candidates to review" style={{ minInlineSize: "30rem" }}>
          <thead>
            <tr>
              <th>Name</th>
              <th>Role</th>
              <th style={{ inlineSize: "9rem" }}>Stage</th>
            </tr>
          </thead>
          <tbody>
            {candidates.slice(0, 3).map((row) => (
              <TableRow
                key={row.id}
                aria-description="Press Enter to open the record"
                aria-current={open && row.id === person?.id ? "true" : undefined}
                onActivate={() => {
                  setPerson(row);
                  setOpen(true);
                }}
              >
                <th scope="row">{row.name}</th>
                <td>{row.role}</td>
                <td>
                  <Pill tone={tones[row.stage]}>{row.stage}</Pill>
                </td>
              </TableRow>
            ))}
          </tbody>
        </Table>
      </LayerCard>
      <SheetRoot open={open} onOpenChange={setOpen}>
        <Sheet>
          {person && (
            <>
              <SheetHeader close>{person.name}</SheetHeader>
              <SheetDescription>
                {person.role}, applied {person.applied}
              </SheetDescription>
              <SheetBody>
                <Tabs
                  aria-label="Record sections"
                  variant="underline"
                  tabs={[
                    { value: "details", label: "Details" },
                    { value: "notes", label: "Notes" },
                  ]}
                >
                  <TabsPanel value="details">
                    <DescriptionList layout="columns">
                      <div>
                        <dt>Stage</dt>
                        <dd>
                          <Pill tone={tones[person.stage]}>{person.stage}</Pill>
                        </dd>
                      </div>
                      <div>
                        <dt>Owner</dt>
                        <dd>{person.owner}</dd>
                      </div>
                      <div>
                        <dt>Email</dt>
                        <dd>{person.email}</dd>
                      </div>
                    </DescriptionList>
                  </TabsPanel>
                  <TabsPanel value="notes">
                    {notes.map(([byline, note]) => (
                      <div key={byline}>
                        <Text variant="secondary" size="sm">
                          {byline}
                        </Text>
                        <Text>{note}</Text>
                      </div>
                    ))}
                  </TabsPanel>
                </Tabs>
              </SheetBody>
              <SheetActions>
                <Button icon={<PencilSimpleIcon />}>Edit</Button>
                <SheetClose render={<Button variant="primary">Done</Button>} />
              </SheetActions>
            </>
          )}
        </Sheet>
      </SheetRoot>
    </>
  );
}

Settings form

Each label sits beside its control while both fit, and goes on top on a narrow screen (FieldGroup orientation="responsive"). Save sends the values by name, with the button busy until the server answers, and a status line says when it is done. Cancel puts back the saved values: the fields mount again with them (a native reset button does not reset Select, NumberField or Switch).

Time zone

The most you take; scheduling stops there.


A summary of your open roles, every Monday.

import { useState } from "react";
import { Button } from "@etched/monet/button";
import { Field, FieldGroup } from "@etched/monet/field";
import { Form } from "@etched/monet/form";
import { Input } from "@etched/monet/input";
import { NumberField } from "@etched/monet/number-field";
import { Select } from "@etched/monet/select";
import { Switch } from "@etched/monet/switch";
import { Text } from "@etched/monet/text";

const zones = { pt: "Pacific (PT)", et: "Eastern (ET)", tw: "Taipei (CST)" };

/** A stand-in for the server: it saves after a moment. */
const save = () => new Promise((resolve) => setTimeout(resolve, 600));

export function SettingsForm() {
  const [busy, setBusy] = useState(false);
  const [status, setStatus] = useState("");
  const [edit, setEdit] = useState(0);

  return (
    <Form
      style={{ display: "grid", gap: "var(--et-space-6)", maxInlineSize: "40rem" }}
      onFormSubmit={async () => {
        setBusy(true);
        setStatus("");
        await save();
        setBusy(false);
        setStatus("Settings saved.");
      }}
    >
      <FieldGroup key={edit} orientation="responsive">
        <Input name="name" label="Display name" defaultValue="Sam Lee" required />
        <Select name="zone" label="Time zone" items={zones} defaultValue="pt" />
        <NumberField name="interviews" label="Interviews per week" description="The most you take; scheduling stops there." defaultValue={6} min={0} max={20} />
        <hr />
        <Field label="Weekly digest" description="A summary of your open roles, every Monday.">
          <Switch name="digest" defaultChecked />
        </Field>
      </FieldGroup>
      <div style={{ display: "flex", flexWrap: "wrap", alignItems: "center", justifyContent: "flex-end", gap: "var(--et-space-2)" }}>
        <Text variant="secondary" role="status">
          {status}
        </Text>
        <Button
          onClick={() => {
            setEdit(edit + 1);
            setStatus("");
          }}
        >
          Cancel
        </Button>
        <Button type="submit" variant="primary" loading={busy}>
          Save
        </Button>
      </div>
    </Form>
  );
}

First run

A page with nothing in it yet: the header names the page, and the empty state says what goes here. Its button is the page's one primary action.

Referrals

People your team refers, and where each one is in the process.

No referrals yet

Refer someone you have worked with. The recruiting team reviews new referrals every day.

import { PlusIcon, UsersThreeIcon } from "@phosphor-icons/react";
import { Button } from "@etched/monet/button";
import { Empty } from "@etched/monet/empty";
import { PageHeader } from "@etched/monet/page-header";

export function FirstRun() {
  return (
    <div style={{ display: "grid", gap: "var(--et-space-6)" }}>
      <PageHeader title="Referrals" description="People your team refers, and where each one is in the process." />
      <Empty
        icon={<UsersThreeIcon />}
        title="No referrals yet"
        description="Refer someone you have worked with. The recruiting team reviews new referrals every day."
        contents={
          <Button variant="primary" icon={<PlusIcon />}>
            Add referral
          </Button>
        }
      />
    </div>
  );
}

App shell

The app's navigation beside the page, with the current page marked; then the page: its header and its content. The trigger beside the breadcrumbs collapses the sidebar to icons; below 48 rem it opens the sidebar as a drawer.

Compiler Engineer

Toronto · Hiring manager: Sam Lee · 2 openings

NameStage
Ana RuizInterviewing
José NúñezScheduled
Lee ParkHired
Mira OseiOffer
import { ChatsCircleIcon, GearIcon, MagnifyingGlassIcon, PlusIcon, TrayIcon } from "@phosphor-icons/react";
import { Avatar } from "@etched/monet/avatar";
import { Breadcrumbs, BreadcrumbsCurrent, BreadcrumbsLink } from "@etched/monet/breadcrumbs";
import { Button } from "@etched/monet/button";
import { LayerCard } from "@etched/monet/layer-card";
import { PageHeader } from "@etched/monet/page-header";
import { Pill, type PillTone } from "@etched/monet/pill";
import { Sidebar, SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupLabel, SidebarHeader, SidebarMenu, SidebarMenuButton, SidebarProvider, SidebarTrigger } from "@etched/monet/sidebar";
import { Table } from "@etched/monet/table";

type Stage = "Scheduled" | "Interviewing" | "Offer" | "Hired";

interface Candidate {
  id: string;
  name: string;
  role: string;
  stage: Stage;
  owner: string;
  email: string;
  applied: string;
}

const candidates: Candidate[] = [
  { id: "ana", name: "Ana Ruiz", role: "ASIC design engineer", stage: "Interviewing", owner: "Sam Lee", email: "ana.ruiz@example.com", applied: "Oct 2" },
  { id: "jose", name: "José Núñez", role: "Firmware engineer", stage: "Scheduled", owner: "Kai Ito", email: "jose.nunez@example.com", applied: "Oct 4" },
  { id: "lee", name: "Lee Park", role: "Thermal engineer", stage: "Hired", owner: "Sam Lee", email: "lee.park@example.com", applied: "Sep 12" },
  { id: "mira", name: "Mira Osei", role: "Physical design lead", stage: "Offer", owner: "Ana Ruiz", email: "mira.osei@example.com", applied: "Sep 20" },
  { id: "noor", name: "Noor Haddad", role: "Verification engineer", stage: "Interviewing", owner: "Kai Ito", email: "noor.haddad@example.com", applied: "Oct 6" },
  { id: "ivan", name: "Ivan Petrov", role: "DFT engineer", stage: "Scheduled", owner: "Sam Lee", email: "ivan.petrov@example.com", applied: "Oct 9" },
  { id: "chen", name: "Chen Wei", role: "Packaging engineer", stage: "Offer", owner: "Ana Ruiz", email: "chen.wei@example.com", applied: "Sep 28" },
  { id: "sara", name: "Sara Lind", role: "Power engineer", stage: "Interviewing", owner: "Kai Ito", email: "sara.lind@example.com", applied: "Oct 11" },
];

const tones: Record<Stage, PillTone> = { Scheduled: "purple", Interviewing: "cyan", Offer: "olive", Hired: "green" };

// Etched's mark, as the sidebar's header shows it.
function Logo() {
  return (
    <svg viewBox="0 0 116 116" fill="currentColor" aria-hidden="true" style={{ flex: "none", inlineSize: "var(--et-control-sm)", blockSize: "var(--et-control-sm)" }}>
      <path
        fillRule="evenodd"
        d="M0.456543 4.50079C0.456543 2.02536 2.4819 0 4.95733 0H111.476C113.952 0 115.977 2.02536 115.977 4.50079V111.02C115.977 113.495 113.952 115.52 111.476 115.52H4.95733C2.4819 115.52 0.456543 113.495 0.456543 111.02V4.50079ZM95.7545 96.8425H20.8667C19.9665 96.8425 19.2914 96.1671 19.2914 95.3419V85.9431C19.2914 85.118 20.0415 84.4429 20.8667 84.4429H83.3549C85.728 84.4429 88.4655 85.5681 90.1794 87.5184L96.7967 94.6953C97.6219 95.5204 96.9548 96.8425 95.7545 96.8425Z"
      />
    </svg>
  );
}

export function AppShell() {
  return (
    // A frame in place of the browser window. In an app, the provider is the page's outer element.
    <SidebarProvider style={{ blockSize: "34rem", overflow: "hidden", borderRadius: "var(--et-card-radius)", boxShadow: "0 0 0 1px var(--et-line)" }}>
      <Sidebar aria-label="Orbit">
        <SidebarHeader>
          <Logo />
          <span className="et-sidebar-label">Orbit</span>
        </SidebarHeader>
        <SidebarContent>
          <SidebarGroup>
            <SidebarGroupLabel>Recruiting</SidebarGroupLabel>
            <SidebarMenu>
              <SidebarMenuButton href="#app-shell" icon={<MagnifyingGlassIcon weight="fill" />} current>
                Tracer
              </SidebarMenuButton>
              <SidebarMenuButton href="#app-shell" icon={<TrayIcon />}>
                SOBO
              </SidebarMenuButton>
              <SidebarMenuButton href="#app-shell" icon={<ChatsCircleIcon />}>
                References
              </SidebarMenuButton>
            </SidebarMenu>
          </SidebarGroup>
          <SidebarGroup>
            <SidebarGroupLabel>Admin</SidebarGroupLabel>
            <SidebarMenu>
              <SidebarMenuButton href="#app-shell" icon={<GearIcon />}>
                Settings
              </SidebarMenuButton>
            </SidebarMenu>
          </SidebarGroup>
        </SidebarContent>
        <SidebarFooter>
          <Avatar name="Sam Lee" size="sm" alt="" />
          <span className="et-sidebar-label">Sam Lee</span>
        </SidebarFooter>
      </Sidebar>
      {/* In an app, this is the page's `main`. */}
      <div style={{ display: "grid", alignContent: "start", gap: "var(--et-space-6)", overflow: "auto", padding: "var(--et-space-6)" }}>
        <PageHeader
          breadcrumbs={
            <div style={{ display: "flex", alignItems: "center", gap: "var(--et-space-2)" }}>
              <SidebarTrigger />
              <Breadcrumbs>
                <BreadcrumbsLink href="#app-shell">Tracer</BreadcrumbsLink>
                <BreadcrumbsCurrent>Compiler Engineer</BreadcrumbsCurrent>
              </Breadcrumbs>
            </div>
          }
          title="Compiler Engineer"
          description="Toronto · Hiring manager: Sam Lee · 2 openings"
          actions={
            <Button variant="primary" icon={<PlusIcon />}>
              Add candidate
            </Button>
          }
        />
        <LayerCard size="none">
          <Table aria-label="Compiler Engineer pipeline">
            <thead>
              <tr>
                <th>Name</th>
                <th style={{ inlineSize: "9rem" }}>Stage</th>
              </tr>
            </thead>
            <tbody>
              {candidates.slice(0, 4).map((person) => (
                <tr key={person.id}>
                  <th scope="row">{person.name}</th>
                  <td>
                    <Pill tone={tones[person.stage]}>{person.stage}</Pill>
                  </td>
                </tr>
              ))}
            </tbody>
          </Table>
        </LayerCard>
      </div>
    </SidebarProvider>
  );
}