Pill
PortedA 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>
);
}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
PillButtonare native buttons:EnterandSpacepress them. Pillis aspanwith no role; screen readers read its text. The remove button is named "Remove" and the text, orremoveLabel.PillButtonis abutton, or a link withrender. A disabled link getsaria-disabled, and a click orEnterdoes nothing.tone,hueandlevelare color only, so put the status or score in the text. SetremoveLabelwhen 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
Pill
| Prop | Type | Default | Description |
|---|---|---|---|
| tone | "neutral" | "red" | "orange" | "amber" | "olive" | "green" | "teal" | "cyan" | "blue" | "purple" | "neutral" | One of Orbit's tones. |
| hue | number | – | A hue (0–360) for custom stages, at the tones' lightness and chroma. Wins over tone. |
| level | 1 | 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" | |
| onRemove | MouseEventHandler<HTMLButtonElement> | – | Adds a remove button (×) at the end, for a filter chip. Then move focus, for example to the next chip's remove button. |
| removeLabel | string | `Remove ${children}` | The remove button's name. Set it when the children are not plain text. |