Skip to content
hightouchUI

Design system

v40.8.0

Borders

Borders define the edge of a surface and separate content within it. A border is three decisions: width, style, and color. Use the hairline token for width, solid for style, and a semantic token for color.

  • Set width with borderWidth (or a side, e.g. borderBottomWidth).
  • Set style with borderStyle and color with borderColor.
  • Default to borderWidth="hairline", borderStyle="solid", borderColor="base.border".

Hairline

The default border width — density-adaptive: 1px on standard displays, 0.5px on high-DPI (≥2x) displays so borders stay crisp. Use for resting borders on surfaces (inputs, selects, comboboxes, textareas, badges, cards, avatars, icon boxes) and dividers; pair with borderStyle="solid" and a semantic borderColor (base.border by default). Menus and popovers are generally borderless — they elevate with shadow instead. Not for focus rings (use the outline shadow). A fixed literal 1px / 2px is a rare exception for emphasis or density-independent alignment — never the default.

It's a borderWidth token backed by the --chakra-border-hairline CSS variable.

Avoid

  • Hardcoding 0.5px or 1px for standard borders — use hairline so density is handled for you.
  • In-between widths like 0.75px or 1.5px.
hairline — token1px · 0.5px on retina
2pxEmphasis

In component themes

In theme component configs, import the $borderHairline CSS variable and compose it into the border shorthand:

Color

Border color is always a semantic token, never a raw hex or palette step. base.border is the default; reach for an intent color only when the border itself communicates status.

  • base.border — the outer edge of a surface (input, card, badge, avatar).
  • base.divider — separators inside a surface (table rows, list items, sections). It's lighter than base.border so internal structure recedes.
  • Intent borders (primary, danger, warning, success, upsell) — only when the border carries meaning, e.g. an invalid field or a selected option.
base.borderDefault resting border on a surface (gray.300).
base.borderHoverHover state for interactive surfaces (gray.400).
base.borderDisabledDisabled surfaces (gray.200).
base.dividerSeparators inside a surface; lighter than base.border (gray.200).
primary.borderSelected or branded surfaces (teal.300).
danger.borderErrors and destructive surfaces (red.200).
warning.borderWarnings (yellow.300).
success.borderSuccess and confirmation (green.300).
upsell.borderUpsell and premium surfaces (purple.300).

Border vs. divider

Use base.border for the edge of a surface and base.divider for separators within it. The divider is one step lighter so the container reads as the dominant shape.

First item
Second item
Third item

Style

  • solid is the default for every border.
  • dashed is reserved for pending, placeholder, or not-yet-committed states (e.g. a pending badge). Don't use it for decoration.
Solid — default
Dashed — pending only

Borders vs. shadows

Borders and shadows do different jobs. A border defines an edge; a shadow lifts a surface off the canvas.

  • Use a hairline border for definition on a flat surface that sits on the page.
  • Use a shadow for elevation (menus, popovers, modals).
  • Some elements use both — inputs pair a hairline border with the buttonShadowLight shadow.
  • Avoid stacking a visible border with a large elevation shadow; it creates a doubled edge.

Do's and don'ts

  • Do default to borderWidth="hairline" + borderStyle="solid" + borderColor="base.border".
  • Do use base.divider for separators inside a surface.
  • Do match the border color to intent (danger.border on an invalid field).
  • Don't hardcode 0.5px or 1px for standard borders — use hairline.
  • Don't use a border to signal focus — use the outline shadow.
  • Don't reach for raw colors (gray.300, hex) when a semantic token exists.