# Borders Default to currentColor

**MUST** · **ID:** `design-border-currentcolor` · **Category:** design
**Source:** [Tailwind](https://tailwindcss.com/docs)
**Interactive version:** https://ui-guides-agent-rules.netlify.app/principles/design-border-currentcolor

> MUST: Never write a border width without a border color in the same breath. In v4 `border-color` defaults to `currentColor`, NOT `gray-200` as in v3 — so a bare `border` inherits the text color, a near-black paragraph gets a near-black box, `text-destructive` turns the outline red, and it can never be a surface-relative hairline in dark mode. Pair the width with a semantic token: `border border-border`. A base-layer rule resetting `border-color` is a migration crutch, not the destination.

Always pair a border width with an explicit border color, because v4 no longer defaults borders to gray-200

This is the classic "we upgraded to v4 and every card grew an ugly black outline" bug. `<div class="border p-4">` used to give you a soft gray hairline; now `border-color` resolves to `currentColor`, so the border inherits the element's text color and a near-black paragraph produces a near-black box. It is not a crash, so it survives code review and ships. Two things make it worse than a one-time visual regression. First, the border now moves whenever the text color moves: put `text-destructive` on that card and the outline turns red with it. Second, it silently defeats dark mode, because a border that tracks the foreground can never be a surface-relative hairline. The fix is a habit, not a config change: never write a border width without a border color in the same breath — `border border-border` — and reach for a semantic token so the hairline flips with the theme. If you are migrating a large v3 codebase and cannot audit every call site at once, the documented stopgap is a base-layer rule setting `border-color` back to your gray token, but treat that as a migration crutch, not the destination.

## Rule snippet

```tsx
<div class="border p-4" />              <!-- v3: gray hairline. v4: currentColor -->
<div class="border border-border p-4" /> <!-- theme-aware in both -->
```

## Bad — do not do this

`design-border-currentcolor-bad`

```tsx
export function BorderCurrentcolorBad() {
  return (
    <div className="w-full max-w-md space-y-4">
      <div className="bg-muted rounded-md p-3 font-mono text-xs overflow-x-auto">
        <pre className="text-error">{`<div class="border p-4">   <!-- width, no color -->`}</pre>
      </div>

      {/* Live: `border` with no color resolves to currentColor, so each card's
          outline inherits whatever text color happens to be on it. */}
      <div className="border p-4 rounded-lg text-foreground">
        <p className="text-sm font-medium">Default card</p>
        <p className="text-xs">
          In v3 this was a soft gray hairline. Now it is the text color: a hard, near-black box.
        </p>
      </div>

      <div className="border p-4 rounded-lg text-destructive">
        <p className="text-sm font-medium">Same markup, error state</p>
        <p className="text-xs">
          Add <code className="font-mono">text-destructive</code> and the border turns red with it. It tracks the
          foreground, not the surface.
        </p>
      </div>

      <p className="text-xs text-error">
        border-color now defaults to currentColor. Every un-colored border grew an outline nobody asked for — and no
        build error said so.
      </p>
    </div>
  );
}
```

## Good — do this

`design-border-currentcolor-good`

```tsx
export function BorderCurrentcolorGood() {
  return (
    <div className="w-full max-w-md space-y-4">
      <div className="bg-muted rounded-md p-3 font-mono text-xs overflow-x-auto">
        <pre className="text-foreground">{`<div class="border border-border p-4">  <!-- width + color -->`}</pre>
      </div>

      {/* The width and the color are always written together. The hairline is
          bound to the surface token, so it flips with the theme. */}
      <div className="border border-border p-4 rounded-lg bg-card text-card-foreground">
        <p className="text-sm font-medium">Default card</p>
        <p className="text-xs text-muted-foreground">
          A hairline that belongs to the surface, not to the text.
        </p>
      </div>

      <div className="border border-border p-4 rounded-lg bg-card text-destructive">
        <p className="text-sm font-medium">Same markup, error state</p>
        <p className="text-xs text-muted-foreground">
          The text turns red. The border does not follow it anywhere.
        </p>
      </div>

      <p className="text-xs text-success">
        Never write a border width without a border color in the same breath. Use a semantic token so it flips with the
        theme.
      </p>
    </div>
  );
}
```

## References

- [Tailwind v4: Upgrade guide](https://tailwindcss.com/docs/upgrade-guide)
- [Tailwind v4: border-color](https://tailwindcss.com/docs/border-color)
- [MDN: border-color](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/border-color)
