A Guide to CSS currentColor for Theming and Inheritance

The CSS currentColor keyword inherits the computed color value and applies it to borders, shadows, and SVG fills without declaring custom properties.

The CSS currentColor keyword is the single most useful colour value you can write today. It lets one declared colour cascade through borders, shadows, fills and strokes without a single token. Change color on a card and every property that says currentColor follows automatically. Here is a card component that does exactly that:

.card {
  color: #1a5276; /* deep blue */
  background: #ffffff;
  border: 2px solid currentColor;
  box-shadow: 0 4px 12px currentColor;
}
.card svg {
  fill: currentColor;
  stroke: currentColor;
}

Change #1a5276 to #c0392b and the border, shadow, and icon all shift together. The alternative with custom properties looks like this:

.card {
  --card-accent: #1a5276;
  color: var(--card-accent);
  background: #ffffff;
  border: 2px solid var(--card-accent);
  box-shadow: 0 4px 12px var(--card-accent);
}
.card svg {
  fill: var(--card-accent);
  stroke: var(--card-accent);
}

Both work. The difference is what you are declaring. currentColor is for single-axis colour inheritance: one colour, many declarations, no tokens. Custom properties are for multi-axis theming: when the accent, the surface and the text colour all vary independently. Use currentColor when the colour is the same everywhere that matters. Use a custom property when you need to flip one axis without touching the others.

The border in the first sample has a contrast ratio of 8.7:1 against the white background (#1a5276 on #ffffff). The box-shadow at that same colour is decorative, not text, so contrast ratio is advisory. It reads as a deliberate tint rather than a grey blur. If you pick a lighter accent, say #5dade2, the contrast drops to 3.1:1, which fails WCAG AA for normal text on the border. The border is not text, but if that border wraps a card title you have a problem. Keep the accent dark enough to carry text, or use currentColor only on non-text surfaces and set a separate color for the text.

The CSS currentColor keyword is a computed value, not a variable

How The Browser Computes currentColor

When the browser resolves currentColor, it does so at the element where the declaration appears, using that element’s computed value of the color property. That is the whole rule. The color property inherits down the DOM, so a color set on a parent reaches the child, and currentColor on the child picks up that inherited value. The computed value stage is where this happens: specified values become computed values, and currentColor is resolved to the color value at that point. The Cascade and Inheritance specification has said this since CSS2. currentColor from CSS Color Module Level 3 merely exposes it as a keyword.

The practical consequence: currentColor is not a token you define and reuse. It is a pointer to a property that already exists. Write border-color: currentColor on a button and the border takes the button’s own color value, whether that value is inherited, declared or set by a user agent style. You do not need a fallback for the keyword itself. Every browser since Internet Explorer 9 supports it. What needs a fallback is a property that does not accept currentColor. None of the colour properties reject it, but shorthand properties can behave differently. box-shadow: 0 0 10px uses currentColor per spec because the colour is omitted. Many authors write box-shadow: 0 0 10px currentColor to be explicit. Both are correct. The explicit version survives minifiers that strip the shorthand colour.

The Failure Case: currentColor Is Not A Variable

The failure case for currentColor is when you expect it to behave like a variable that resolves where defined. It does not. Put currentColor inside a custom property and the resolution happens where the var() is called, not where the custom property is declared. This trips people who write –border: currentColor on a parent, then use var(–border) on a child with a different color. The child’s currentColor resolves to the child’s color, not the parent’s. The rule is simple: currentColor always means the color of the element that uses it, never the element that declared it.

currentColor inheritance in SVG follows the color property, not fill or stroke

SVG has a different default. In SVG 1.0, currentColor was defined as the initial value for fill, stroke, stop-color, flood-color and lighting-color. An SVG element with no explicit fill uses currentColor, which resolves to the color property. The inheritance chain runs through the color property, not through the SVG presentation attributes. A <rect> inside a <g> with color: #333 gets a #333 fill if no fill is set. The <g> inherits color from its parent, and the rect’s currentColor resolves to that inherited color. If you set fill=”red” on the <g> but no color, the rect still uses currentColor, which resolves to the nearest ancestor’s color, not the <g>’s fill.

Dark Mode And The CanvasText Default

When no color is specified anywhere up the chain, currentColor resolves to the initial value of color, which is CanvasText. In practice that is near-black in light mode and near-white in dark mode, determined by the user’s system. This is why SVG icons with no fill and no color appear black on a light background and white on a dark one. The color-scheme property changes which system colour is used, but it does not change the fact that currentColor is the initial value. Declare color on the SVG or an ancestor if you want a specific colour. Do not rely on the default unless you want the system’s text colour.

The contrast ratio of CanvasText on a white background is around 21:1 (near-black on white). In dark mode the ratio flips to the same value against a near-white foreground. That is why SVG icons using currentColor work across themes without extra CSS. You get the text colour for free, and the icon matches whatever text sits next to it. The cost: you cannot control the exact hue. If your brand needs a specific blue, set color explicitly.

currentColor vs custom properties for theming decisions

One Axis Or Many

Custom properties inherit and cascade, enabling runtime theming and component-scoped tokens. The key difference: currentColor is a single value that mirrors color, while a custom property can hold any value and be named anything. For theming, the decision is about axes. If your theme has one accent colour that affects borders, shadows and icons, currentColor is enough. If your theme has a separate surface colour, text colour and accent colour, each of those needs its own custom property. The point where currentColor fails is the point where the accent differs from the text colour. A card with color: #333 and border-color: currentColor gets a dark grey border. If you want a blue border with grey text, currentColor cannot do that. You need a custom property for the border colour.

The performance angle is negligible. Resolving currentColor is a computed-value operation, not a layout or paint cost. It happens during style resolution, before layout, and does not trigger a reflow. Custom properties are also resolved at computed-value time. Neither is a paint or layout cost. The real cost difference is in authoring: currentColor saves you from declaring a token when you only need one colour. Custom properties cost a declaration and a var() call, and they add a layer of indirection that can make debugging harder. The cascade is the same for both. Specificity rules apply to the property, not the value. A color: red on a more specific selector overrides the color that currentColor would otherwise inherit.

When To Escalate To A Custom Property

The practical guide: use currentColor for anything that should match the text colour without extra tokens. That includes border-color, box-shadow, text-decoration-color, outline-color, and SVG fill and stroke. Use custom properties for anything that needs to differ from the text colour, or when you need to flip multiple colours in a theme. A theme with dark and light modes benefits from custom properties for the surface and text colours, but the accent can still be currentColor if the accent follows the text. The moment the accent is a separate hue, it becomes a custom property.

currentColor border color patterns and fallbacks

The Fallback Declaration Order

A common pattern: declare a hardcoded colour first, then override with currentColor. This gives a fallback for old browsers, though every browser since 2011 supports currentColor for all colour properties. The fallback is more useful when you are not sure whether a shorthand property applies currentColor as you expect. For example, border: 2px solid #333; border-color: currentColor; sets a solid dark border in a browser that ignores currentColor, and a currentColor border in a modern one. The pattern is a declaration order trick: the hardcoded value comes first, the currentColor second, and the cascade lets the latter win where supported.

A failure case with border-color: forgetting that the shorthand border: 2px solid; with no colour uses currentColor per spec, but only if you omit the colour. Write border: 2px solid red; border-color: currentColor; and the second declaration wins. You get a currentColor border. That is expected. The mistake is writing border-color: currentColor and then declaring border: 2px solid red later in the stylesheet. The shorthand resets border-color to red, and your currentColor is gone. Source order and cascade rules decide, not the keyword’s intent.

Another failure case: using currentColor in a box-shadow and expecting it to inherit from a parent when the shadow is on a child. The shadow’s currentColor resolves to the child’s color, not the parent’s, unless the child inherits color. Since color inherits by default, the child usually has the same color as the parent, so the shadow matches. But if the child has color: red, the shadow is red, even if the parent’s border is blue. That is correct behaviour. The rule is the same as everywhere else: currentColor is the element’s own color.

Using CSS currentColor in custom properties and var() resolution

When currentColor appears inside a custom property value, the browser does not resolve it at declaration. The custom property stores the token sequence currentColor as a string. When a var() references that custom property, the browser substitutes the value at the point of use, then resolves currentColor against the element where var() is called. This is a subtle but critical difference. Consider:

.parent {
  color: blue;
  --accent: currentColor;
}
.child {
  color: red;
  border-color: var(--accent);
}

The .child border is red, not blue. The –accent value is currentColor, and when .child uses var(–accent), the currentColor resolves against .child’s color, which is red. If you expected blue, you have hit the most common custom property interaction bug. The rule: currentColor resolves where the var() is invoked, not where the custom property is defined. This is true for any property that accepts a <color> value, including fill, stroke, border-color and box-shadow.

The consequence for theming: you cannot define a token as –accent: currentColor and expect it to follow the element that declares it. If you want a token that means “the current text colour”, use currentColor directly in the property, not through a custom property. The custom property adds indirection with no benefit in this case. The exception is when you want to conditionally switch between currentColor and a fixed colour, like –accent: var(–use-current, currentColor);. That works, but the fallback value in var() is substituted first, and then currentColor resolves in the same way.

currentColor vs custom properties in the cascade and specificity

The cascade treats currentColor and custom properties identically in terms of origin, layer, specificity and source order. A declaration with border-color: currentColor has the same specificity as border-color: var(–x). Neither is special. What differs is the resolution of the value. currentColor resolves against the element’s computed color; var() resolves against the custom property’s computed value, which may itself contain currentColor or another var(). The cascade does not care about the value’s content. It only decides which declaration wins. This is why currentColor does not have a specificity problem. It is a value, not a selector.

currentColor Is Not A Scoped Variable

A common misconception: currentColor is a variable with its own scope. It is not. It is a keyword that expands to the computed value of color at the point of use. The expansion happens after the cascade has decided which color declaration wins. So if two rules target the same element, one setting color: red with specificity (0,1,0) and another setting color: blue with (0,2,0), the blue wins, and any currentColor on that element resolves to blue. The specificity rules apply to the color property, and currentColor merely reflects the winner.

That is also why currentColor is safe to use in accent-color and color-scheme contexts, though the semantics differ. accent-color controls form controls like checkboxes and radio buttons. Setting accent-color: currentColor makes the control match the surrounding text colour. The color-scheme property, by contrast, chooses which system palette the browser uses for form controls and scrollbars. It does not accept currentColor directly, but it affects the CanvasText value that currentColor falls back to when no color is set. The combination gives you form controls that follow your theme without extra declarations.

Common mistakes with currentColor and how to diagnose them

Three Mistakes That Break Colour Inheritance

The most frequent mistake is expecting currentColor to resolve where a custom property is defined. We covered that. The second is assuming box-shadow defaults to currentColor in older syntax. Per CSS Color Level 3, a box-shadow with no colour uses currentColor, and this has been consistent. The mistake is writing box-shadow: 0 0 10px #000 and then wondering why the shadow is black. You explicitly set a colour. currentColor is only used when the colour is omitted. The fix: write box-shadow: 0 0 10px or box-shadow: 0 0 10px currentColor.

The third mistake is using currentColor in a context where the color property is not inherited, or where a more specific rule resets it. For example, a button inside a card with color: #333 on the card may get color: buttontext from the user agent stylesheet, which overrides the inherited #333 because the UA style has higher origin. The button’s border-color: currentColor then resolves to buttontext, not #333. The fix: explicitly set color: inherit on the button or set the button’s color to your theme colour. The cascade decides, and currentColor follows the winner.

The failure case for SVG: a nested element has its own color that differs from the parent. The child’s fill: currentColor resolves against the child’s color, not the parent’s. If you want the child to inherit the parent’s fill colour, set fill: inherit or do not set color on the child. The rule is the same as HTML: currentColor is the element’s own color value. Inheritance of color is what makes it look like it follows the parent.

FAQ: currentColor inheritance and theming

Does currentColor inherit from parent to child?

currentColor does not inherit as a value. The color property inherits. When a child uses currentColor, it resolves to the child’s computed color, which is inherited from the parent unless overridden. The effect: currentColor on a child matches the parent’s color when no other color is set on the child.

Can I use currentColor in a custom property?

Yes, but resolution happens at the point of var() invocation, not where the custom property is declared. A –x: currentColor on a parent does not make var(–x) on a child resolve to the parent’s colour. It resolves to the child’s color.

Does box-shadow use currentColor by default?

Yes, per CSS Color Module Level 3, a box-shadow with no colour specified uses currentColor. Explicitly setting a colour overrides this.

What is the fallback for browsers without currentColor support?

Every browser since 2011 supports currentColor. For older browsers, declare a hardcoded colour before the currentColor declaration, as in border: 2px solid #333; border-color: currentColor;. The fallback only matters for browsers that predate Internet Explorer 9.

How does currentColor interact with SVG fill and stroke?

In SVG, fill and stroke default to currentColor if not specified. The keyword resolves against the element’s color property, not the fill or stroke of an ancestor. Set color on the SVG or an ancestor to control the resolved value.

Practical recommendation: start with currentColor, escalate to custom properties

For any component where borders, shadows and icons should match the text colour, write currentColor first. It is one keyword, requires no token, and the computed value resolves exactly where you expect. When the theme requires a colour that differs from the text, introduce a custom property for that specific axis. Do not declare a custom property for every colour in your component. Declare one for each independent axis. A card with a text colour, a surface colour and an accent colour needs three tokens, not seven. The accent that matches text does not need a token at all. Use currentColor.

The performance benefit is modest but real: fewer custom properties means fewer var() substitutions in the computed-value stage, which is cheap but not free. The maintainability benefit is larger. A developer reading border-color: currentColor knows the border follows the text. A developer reading border-color: var(–accent) has to find where –accent is defined and whether it differs from color. The declarative intent is visible in the value itself. That is the CSS way: describe the relationship, not the recipe.

When you do need a custom property, the fallback pattern is var(–accent, #333). That gives a hardcoded fallback if the custom property is not defined. For currentColor, no fallback is needed. It is a CSS-wide keyword with universal support. The only time a fallback matters is when a property’s shorthand omits the colour and you want to be explicit, which is a readability choice, not a compatibility one.

The one thing to do next

Open your stylesheet. Find every place you declared the same hex colour on two or more properties of the same element. Replace the second and later occurrences with currentColor. Keep the first declaration as color. Then test in dark mode. The contrast ratio of currentColor against the background changes with the theme, and you need to verify the border and shadow still meet your accessibility threshold. Check the contrast ratio of the color value against the background. If it fails WCAG AA, darken the color until it passes. The border and shadow follow automatically. That is the entire point of currentColor.