Scale
The numeric scales of the system — space, corner radius, and elevation. Color and type have their own foundation pages; this one is everything measured in rem. Click a token to copy its utility class.
Radius
Closed set in theme.css: sm / base /lg, plus Tailwind's full for pills.xs, md, xl, 2xl, and3xl are gone. Every corner in the system — controls, cards, overlays, nested surfaces — must use this set.
- Use only
rounded-sm/rounded-base/rounded-lg/rounded-fullfor theouter edge. None of the removed stops. - Draw rings on the outer wrapper; inset the control (Input uses
p-0.5/ 2px) so fill and autofill sit inside the hairline. Documented paddings already include that inset — seeInput.
Squircle
The squircle utility (corner-shape: superellipse(1.1)) softens a rounded-* edge into a superellipse. Use it only forinteractive chrome — hover fills, press surfaces, focus rings, and other feedback layers — never for resting media or static cards.
- Yes: Button hover / effect sheen, Input focus ring chrome, CloseButton hover chip, Toggle press surface.
- No: Avatar photos (true
rounded-fullcircle), menus, dialogs, toasts, and other resting bordered panels. - Pair with a radius token —
squirclealone does not set size.
Concentric correction
Whenever a ring, border, or nested surface sits inside a rounded outer edge, the inner curve must stay parallel. The universal rule — the gap is thetotal edge-to-edge distance (border / ring width + padding):
innerRadius = max(0px, outerRadius − gap)
- Always derive it from the token with
calc— e.g.rounded-[calc(var(--radius-base)-2px)]for a 2px inset insidebase— never a hardcoded resolved px. The correction then tracks the token if the scale ever changes, and the class itself documents the derivation. Concentric corrections are the only allowed arbitrary radii; do not invent unrelated stops. - Clamp with
max(0px, …)whenever the gap can meet or exceed the radius — a negativeborder-radiussilently invalidates the whole declaration. The0pxunit is required:max(0, …)is invalid inside CSS math and gets dropped too. For statically known-positive cases (all current component insets) the barecalcis fine. - Unequal horizontal / vertical gaps → correct each axis with the slash form:
border-radius: calc(R − gapX) / calc(R − gapY). - Keep any radius ≤ half the element's shortest side — past that the browser rescales all four corners and the derived value silently drifts.
fullpills are the exception to subtraction: nested surfaces inside a pill stayfull. - Plain round corners: browsers already offset inset box-shadows concentrically. Prefer a real nested element (or a
::beforefill) atouter − gapso backgrounds never paint over the hairline. - When a ring does use
squircle, enginesscale the inset shadow instead of offsetting it (csswg #12961), so the ring thickens at the corners. Always cover the inner curve with a::beforeat the concentric radius (Inputlg:calc(var(--radius-base)-1px)rest /calc(var(--radius-base)-2px)focused). Never putsquircle+border/ring-inseton the same box without that cover — the cards below stay plain round.
Spacing
Pho does not ship a custom spacing scale — it uses Tailwind's default (--spacing = 0.25rem / 4px). Padding, margin, and gap utilities (p-4, gap-2, …) all multiply that unit. Prefer even steps on the 4px grid; the bars below are the sizes components reach for most often.
Elevation
Shadows are for floating layers only — menus, popovers, dialogs, drawers. Cards and buttons stay flat; separation is a hairline border (ring-pho-secondary / border-pho-secondary). The stops in use today are shadow-pho-lg andshadow-pho-xl.