Monet
@etched/monet 0.0.1

Pill

Ported

A status chip: a tone's fill and ink, 24 px high, 13 px text. A style replaces the hue. With onRemove, a filter chip.

ActivatingInterviewingHired
import { Pill } from "@etched/monet/pill";

export function Preview() {
  return (
    <div className="row">
      <Pill tone="blue">Activating</Pill>
      <Pill tone="cyan">Interviewing</Pill>
      <Pill tone="green">Hired</Pill>
    </div>
  );
}

Installation

import { Pill, PillButton } from "@etched/monet/pill";

Examples

Tones

neutralredorangeamberolivegreentealcyanbluepurple
import { Pill, type PillTone } from "@etched/monet/pill";

const tones: PillTone[] = ["neutral", "red", "orange", "amber", "olive", "green", "teal", "cyan", "blue", "purple"];

export function Tones() {
  return (
    <div className="row">
      {tones.map((tone) => (
        <Pill key={tone} tone={tone}>
          {tone}
        </Pill>
      ))}
    </div>
  );
}

Custom hues

A hue (0–360) colors a custom stage at the tones' lightness and chroma.

Custom stage 1Custom stage 2Custom stage 3Custom stage 4Custom stage 5Custom stage 6
import { Pill } from "@etched/monet/pill";

// Orbit's custom-stage hues, between the fixed tones and never near red.
const hues = [236, 286, 134, 195, 99, 165];

export function CustomHues() {
  return (
    <div className="row">
      {hues.map((hue, i) => (
        <Pill key={hue} hue={hue}>
          Custom stage {i + 1}
        </Pill>
      ))}
    </div>
  );
}

Orbit pipeline stages

To DoActivatingSchedulingScheduledIn TouchInterviewingReferences checkNo ResponseRevisitHiredWe PassedDon't Contact
import { Pill, type PillTone } from "@etched/monet/pill";

// Orbit's pipeline stages (talent/src/types.ts statusTones) and Ashby's statuses.
const stages: [string, PillTone][] = [
  ["To Do", "neutral"],
  ["Activating", "blue"],
  ["Scheduling", "orange"],
  ["Scheduled", "purple"],
  ["In Touch", "teal"],
  ["Interviewing", "cyan"],
  ["References check", "cyan"],
  ["No Response", "amber"],
  ["Revisit", "olive"],
  ["Hired", "green"],
  ["We Passed", "red"],
  ["Don't Contact", "red"],
];

export function OrbitPipelineStages() {
  return (
    <div className="row">
      {stages.map(([label, tone]) => (
        <Pill key={label} tone={tone}>
          {label}
        </Pill>
      ))}
    </div>
  );
}

Ashby statuses

LeadActiveHiredArchived
import { Pill, type PillTone } from "@etched/monet/pill";

const ashby: [string, PillTone][] = [
  ["Lead", "blue"],
  ["Active", "cyan"],
  ["Hired", "green"],
  ["Archived", "red"],
];

export function AshbyStatuses() {
  return (
    <div className="row">
      {ashby.map(([label, tone]) => (
        <Pill key={label} tone={tone}>
          {label}
        </Pill>
      ))}
    </div>
  );
}

Buttons

PillButton is a pill you can press, such as your own follow-up that opens its dialog. It is the size of a Pill, so a row of both keeps its height. With render, it is a link, such as a role that opens its page. A disabled link stays in its place but does not open.

Sam Lee · Oct 14 · SentCompiler EngineerClosed role
import { useState } from "react";
import { Button } from "@etched/monet/button";
import { Dialog, DialogActions, DialogClose, DialogDescription, DialogRoot, DialogTitle } from "@etched/monet/dialog";
import { Pill, PillButton } from "@etched/monet/pill";

export function Buttons() {
  const [open, setOpen] = useState(false);

  return (
    <div className="row">
      <PillButton tone="blue" onClick={() => setOpen(true)}>
        Me · Oct 16, 6:00 PM · Queued
      </PillButton>
      <Pill>Sam Lee · Oct 14 · Sent</Pill>
      <PillButton render={<a href="#role-compiler" />}>Compiler Engineer</PillButton>
      <PillButton disabled render={<a href="#role-closed" />}>
        Closed role
      </PillButton>
      <DialogRoot open={open} onOpenChange={setOpen}>
        <Dialog size="sm">
          <DialogTitle>Follow-up</DialogTitle>
          <DialogDescription>Queued for Oct 16 at 6:00 PM.</DialogDescription>
          <DialogActions>
            <DialogClose render={<Button>Close</Button>} />
          </DialogActions>
        </Dialog>
      </DialogRoot>
    </div>
  );
}

Filter chips

onRemove adds a remove button, named "Remove Hardware" from the text. size="sm" makes the chips 28 px, the height of the small button beside them. After a removal the app moves focus: here to the next chip, or back to the button.

HardwareSiliconSoftware
import { FunnelIcon } from "@phosphor-icons/react";
import { useRef, useState } from "react";
import { Button } from "@etched/monet/button";
import { Pill } from "@etched/monet/pill";

const teams = ["Hardware", "Silicon", "Software"];

export function FilterChips() {
  const [filters, setFilters] = useState(teams);
  const row = useRef<HTMLDivElement>(null);

  const remove = (team: string, button: HTMLButtonElement) => {
    const buttons = [...(row.current?.querySelectorAll("button") ?? [])];
    const i = buttons.indexOf(button);
    (buttons[i + 1] ?? buttons[i - 1])?.focus();
    setFilters(filters.filter((f) => f !== team));
  };

  return (
    <div className="row" ref={row}>
      <Button size="sm" icon={<FunnelIcon />} onClick={() => setFilters(teams)}>
        Reset filters
      </Button>
      {filters.map((team) => (
        <Pill key={team} size="sm" onRemove={(event) => remove(team, event.currentTarget)}>
          {team}
        </Pill>
      ))}
    </div>
  );
}

Sizes

xs (24 px) for statuses in text and table rows; sm (28 px) beside 28 px controls, like Button size="sm". A pill button takes the size too.

Onsite loop
Onsite loop
import { Button } from "@etched/monet/button";
import { Pill, PillButton } from "@etched/monet/pill";

export function Sizes() {
  return (
    <div className="rows">
      <div className="row">
        <Pill tone="cyan">Onsite loop</Pill>
        <PillButton tone="blue">Firmware Engineer</PillButton>
      </div>
      <div className="row">
        <Pill size="sm" tone="cyan">
          Onsite loop
        </Pill>
        <PillButton size="sm" tone="blue">
          Firmware Engineer
        </PillButton>
        <Button size="sm">Small button</Button>
      </div>
    </div>
  );
}

Scores

A level is a step on the green ramp: the pill becomes a small score box with tabular figures.

1234
import { Pill } from "@etched/monet/pill";

export function Scores() {
  return (
    <div className="row">
      <Pill level={1}>1</Pill>
      <Pill level={2}>2</Pill>
      <Pill level={3}>3</Pill>
      <Pill level={4}>4</Pill>
    </div>
  );
}

Accessibility

  • The remove button and PillButton are native buttons: Enter and Space press them.
  • Pill is a span with no role; screen readers read its text. The remove button is named "Remove" and the text, or removeLabel.
  • PillButton is a button, or a link with render. A disabled link gets aria-disabled, and a click or Enter does nothing.
  • tone, hue and level are color only, so put the status or score in the text. Set removeLabel when the children are not plain text.
  • After a remove, move focus, for example to the next chip's remove button, or to the control before the chips when none are left.

API reference

PillButton

PropTypeDefaultDescription
tone"neutral" | "red" | "orange" | "amber" | "olive" | "green" | "teal" | "cyan" | "blue" | "purple""neutral"One of Orbit's tones.
huenumber–A hue (0–360) for custom stages, at the tones' lightness and chroma. Wins over tone.
level1 | 2 | 3 | 4–A step on the green ramp, for an ordinal score such as conviction 1–4. Wins over hue and tone. A Pill with a level is a small score box, and a list option with one takes the fill across its row.
size"xs" | "sm""xs"
renderReactElement<{ className?: string }>–Renders another element with the pill button's look, such as a link: render={<a href="#role/12" />}. It gets no type, and aria-disabled (its click stops) in place of disabled.

Pill

PropTypeDefaultDescription
tone"neutral" | "red" | "orange" | "amber" | "olive" | "green" | "teal" | "cyan" | "blue" | "purple""neutral"One of Orbit's tones.
huenumber–A hue (0–360) for custom stages, at the tones' lightness and chroma. Wins over tone.
level1 | 2 | 3 | 4–A step on the green ramp, for an ordinal score such as conviction 1–4. Wins over hue and tone. A Pill with a level is a small score box, and a list option with one takes the fill across its row.
size"xs" | "sm""xs"
onRemoveMouseEventHandler<HTMLButtonElement>–Adds a remove button (×) at the end, for a filter chip. Then move focus, for example to the next chip's remove button.
removeLabelstring`Remove ${children}`The remove button's name. Set it when the children are not plain text.