Borders Default to 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
<div class="border p-4" /> <!-- v3: gray hairline. v4: currentColor -->
<div class="border border-border p-4" /> <!-- theme-aware in both -->Bad
Good
Why it matters
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.