Pill buttons

The signature interaction of trc-ui: a thin pill with a round accent dot. On hover, the dot stretches until it fills the pill and the label turns dark.

Outline and solid

Hover the pills, or reach them with the Tab key: keyboard focus plays the same animation. The solid pill marks the main action; keep it to one per screen.

TSDemo.tsx
<PillButton solid>Create an account</PillButton>
<PillButton>Get started</PillButton>

Compact size

TSDemo.tsx
<PillButton size="sm">Sign in</PillButton>
<PillButton size="sm" solid>Get started</PillButton>

As a link

PillLink renders the same pill as an <a>. Pass your router's link component with as to keep client-side navigation.

TSDemo.tsx
<PillLink href="#">Read the docs</PillLink>
import Link from "next/link";
import { PillLink } from "trc-ui";

<PillLink as={Link} href="/pricing">See pricing</PillLink>

The Back exception

Pills carry no icon other than their arrow. The only exception is going back, with a left arrow.

TSDemo.tsx
import { ArrowLeft, PillButton } from "trc-ui";

<PillButton icon={<ArrowLeft />}>Back</PillButton>

Loading and disabled

loading replaces the arrow with a spinner, disables the button and announces it as busy. A disabled pill does not stretch on hover.

TSDemo.tsx
<PillButton loading>Sending</PillButton>
<PillButton disabled>Unavailable</PillButton>

How the stretch works

The dot and the fill are a single shape that grows from the dot's width to the full pill, under the border, with an ease-in-out-quart curve over 500 ms. The label darkens 150 ms after the fill starts, and lightens right away when it leaves, so it stays readable at every frame. On touch screens there is no hover: the pill stays at rest. With reduced motion, the change is instant.

CSSTune the motion
:root {
  --trc-ease: cubic-bezier(0.77, 0, 0.175, 1);
  --trc-duration: 500ms;
}

PillButton

PropTypeDefaultDescription
solidbooleanfalseWhite background: the main action of a screen. Use one per screen at most.
size"md" | "sm""md"56 px, or 40 px for headers and toolbars.
iconReactNode–Replaces the arrow in the dot. Reserved for “Back” (left arrow).
loadingbooleanfalseShows a spinner in the dot, disables the button and sets aria-busy.
type"button" | "submit" | "reset""button"Defaults to button: a pill never submits a form by accident.
…propsComponentProps<"button">–Any native button attribute, and a ref.

PillLink

PropTypeDefaultDescription
solidbooleanfalseWhite background: the main action of a screen. Use one per screen at most.
size"md" | "sm""md"56 px, or 40 px for headers and toolbars.
iconReactNode–Replaces the arrow in the dot. Reserved for “Back” (left arrow).
asElementType"a"Your router's link component (Next.js, React Router…).
…propsComponentProps<typeof as>–Props of the rendered element: href, target…