Pill
A floating lozenge holding a small amount of live information — a recording timer, a boarding gate, a build that is still running.
import { Pill } from 'neba';
<Pill startIcon={<DotIcon />} color="danger">
Recording
</Pill>;Three slots: a leading glyph, a middle, a trailing one. Give it onClick and the middle becomes a real button; give it details and it grows downward into a second half.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| variantshared | 'solid' | 'outline' | 'text' | 'solid' | Weight of the surface, said the way a *control* says it: the surface takes the tint, as on Button and Chip |
| sizeshared | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'md' | Height and type scale |
| colorshared | 'primary' | 'secondary' | 'success' | 'warning' | 'danger' | 'info' | 'secondary' | Semantic colour role. `secondary` here rather than `primary`, because the object this shape is borrowed from is very nearly neutral black |
| densityshared | 'default' | 'compact' | 'default' | Padding only — never the height, never the type scale |
| elevationshared | 0 | 1 | 2 | 3 | 2 | Drop shadow depth. `2` against the `0` everything else defaults to, and not an inconsistency: a Pill is defined by not being part of the page, and a lozenge floating flat on the content it floats over reads as a mistake |
| startIcon | ReactNode | — | The leading slot — a glyph, an avatar, a status dot, a small image |
| endIcon | ReactNode | — | The trailing slot. Outside the pressable area, so it can be a control of its own |
| details | ReactNode | — | The second half, revealed when `expanded`. The pill grows downward into it rather than swapping to a different shape: one object saying more, which is the whole idea being borrowed |
| expanded | boolean | false | Whether `details` is showing |
| positionshared | 'static' | 'sticky' | 'fixed' | 'static' | How it sits in the page’s scroll. `fixed` pins it against the viewport and centres it horizontally, which is the arrangement this shape exists for |
| sideshared | 'top' | 'bottom' | 'top' | Which edge it is held against when `position` is not `static` |
| onClick | MouseEventHandler | — | Passing it makes the row a real button. `endIcon` stays outside it: a `<button>` inside a `<button>` is markup the browser rewrites on parse |
| children | ReactNode | — | The middle: a line of text, a pair of small readouts |
Every native <div> attribute passes through.
Examples
Compact and expanded
The pair is the whole idea being borrowed. The pill grows into its second half by animating a measured height — exactly as an Accordion panel does, and for the same reason: nothing is transformed, so no text is ever resampled on the way.
The details area is inert while it is closed, not merely aria-hidden. A zero-height box is one its content is still perfectly focusable inside, and aria-hidden alone would leave a keyboard reader tabbing into something their screen reader has been told does not exist.
Weights and scales
A Pill is a control as far as colour goes, not a container: its surface is the thing being coloured, exactly as on a Button or a Chip. color defaults to secondary here rather than primary, because the object this shape is borrowed from is very nearly neutral black.
Pinned
position and side are the same vocabulary Toolbar uses. fixed pins it against the viewport and centres it horizontally, which is the arrangement this shape exists for — and it is centred with auto margins rather than by translating it half its own width, so it stays centred under RTL.
<Pill position="fixed" side="top" startIcon={<BuildIcon />} color="info">
Building — 2 of 7
</Pill>The round shape
Every other control in the library is held just short of the 50% radius that would make it a pill, because the flat run along its top and bottom edge is what still reads as a sheet with the corners cut off it. This is the exception the rule is drawn against, and it works for the same reason the rule does: a Pill is not a sheet lying on the page. It is an object hovering over one, and an object hovering over the page should not look like it was cut from the same material.
elevation defaults to 2 here for that reason, against the 0 everything else defaults to. It is not an inconsistency — a lozenge floating flat on the content it is floating over reads as a mistake.