Shadows
Shadow utilities add depth with box-shadow. Each size is a token, so every shadow-md on a page can be retuned in one place.
How shadows work
Section titled “How shadows work”- Sizes.
sm,md,lg,xl,heroandmonstername entries of$shadow-sizes. Each entry has an x offset, y offset, blur and spread. - Colours.
default,dark,darkeranddarkestname entries of$shadow-colors, from 10% to 50% black. - Tokens. The default colour is emitted as
--nu-shadow-{size}, andshadow-{size}outputsvar(--nu-shadow-{size}). The darker colour classes and inset shadows output a plainbox-shadowvalue.
| Class | Meaning | Examples |
|---|---|---|
shadow-{size} |
Outer shadow in the default colour | shadow-sm, shadow-lg |
shadow-{size}-{colour} |
Outer shadow in a darker colour | shadow-md-dark, shadow-xl-darkest |
shadow-inset-{size} |
Inner shadow | shadow-inset-sm |
shadow-inset-{size}-{colour} |
Inner shadow in a darker colour | shadow-inset-md-darker |
elevation-{level} |
Shadow plus a small z-index, levels 1 to 5 | elevation-3 |
shadow-none |
Removes the shadow | shadow-none |
There is no bare shadow class; write a size.
Generated from the compiled core build.
| Key | Token | Value |
|---|---|---|
sm | --nu-shadow-sm | 0 1px 2px 0 rgba(0, 0, 0, 0.1) |
md | --nu-shadow-md | 0 4px 6px -1px rgba(0, 0, 0, 0.1) |
lg | --nu-shadow-lg | 0 10px 15px -3px rgba(0, 0, 0, 0.1) |
xl | --nu-shadow-xl | 0 20px 25px -5px rgba(0, 0, 0, 0.1) |
hero | --nu-shadow-hero | 0 20px 25px 5px rgba(0, 0, 0, 0.1) |
monster | --nu-shadow-monster | 0 20px 55px 20px rgba(0, 0, 0, 0.1) |
<div class="flex wrap gap-8 p-4"> <div class="shadow-sm p-4 bg-surface rounded">shadow-sm</div> <div class="shadow-md p-4 bg-surface rounded">shadow-md</div> <div class="shadow-lg p-4 bg-surface rounded">shadow-lg</div> <div class="shadow-xl p-4 bg-surface rounded">shadow-xl</div></div>.card { @include apply(shadow(sm));}.menu { @include apply(shadow(lg));}hero and monster have a positive spread and a large blur, for large floating panels. Give them room: the shadow extends well beyond the element.
<div class="flex wrap gap-16 p-12"> <div class="shadow-hero p-6 bg-surface rounded">shadow-hero</div> <div class="shadow-monster p-6 bg-surface rounded">shadow-monster</div></div>Colours
Section titled “Colours”Add -dark, -darker or -darkest to a size for a stronger shadow. Use them on darker backgrounds, where the default 10% black is hard to see.
<div class="flex wrap gap-8 p-4 bg-alternate rounded"> <div class="shadow-md p-4 bg-surface rounded">shadow-md</div> <div class="shadow-md-dark p-4 bg-surface rounded">shadow-md-dark</div> <div class="shadow-md-darker p-4 bg-surface rounded">shadow-md-darker</div> <div class="shadow-md-darkest p-4 bg-surface rounded">shadow-md-darkest</div></div>.panel { @include apply(shadow(md, darker));}Inset shadows
Section titled “Inset shadows”<div class="flex wrap gap-4"> <div class="shadow-inset-sm p-4 bg-surface rounded">shadow-inset-sm</div> <div class="shadow-inset-md-dark p-4 bg-surface rounded">shadow-inset-md-dark</div></div>.well { @include apply(shadow-inset(sm));}Elevation
Section titled “Elevation”elevation-1 to elevation-5 pair a shadow with a z-index equal to the level, so a more raised sibling also stacks above a less raised one.
| Class | Shadow | z-index |
|---|---|---|
elevation-1 |
var(--nu-shadow-sm) |
1 |
elevation-2 |
var(--nu-shadow-md) |
2 |
elevation-3 |
var(--nu-shadow-lg) |
3 |
elevation-4 |
var(--nu-shadow-xl) |
4 |
elevation-5 |
var(--nu-shadow-monster) |
5 |
The z-index is a small local scale for ordering raised siblings, not a page layer. For dropdowns, modals and other layers, add a level class such as z-dropdown; level classes come later in the stylesheet and override the elevation z-index. See Z-index. Elevation only stacks elements that are positioned (relative, absolute and so on) or are flex or grid items.
<div class="flex gap-8 p-4"> <div class="elevation-1 relative p-4 bg-surface rounded">elevation-1</div> <div class="elevation-3 relative p-4 bg-surface rounded">elevation-3</div> <div class="elevation-5 relative p-4 bg-surface rounded">elevation-5</div></div>.dropdown-panel { @include apply(elevation(3));}Removing a shadow
Section titled “Removing a shadow”shadow-none sets box-shadow: none. It comes after the shadow and elevation classes, so it removes their shadow without !important. With elevation-* it removes the shadow and keeps the z-index.
State variants
Section titled “State variants”| Variant | Classes |
|---|---|
hover:, focus: |
Every size and colour: hover:shadow-lg, focus:shadow-md-dark |
active: |
Every size and colour: active:shadow-sm |
Inset shadows and shadow-none have no state variants.
<div class="flex wrap gap-8 p-4"> <div class="shadow-sm hover:shadow-xl p-4 bg-surface rounded">Hover me</div> <button class="shadow-md active:shadow-sm" type="button">Press me</button></div>.card { @include apply(shadow(sm), shadow(xl) hover);}Responsive variants
Section titled “Responsive variants”Outer shadow classes, their hover: and focus: forms and shadow-none have responsive variants with an @{breakpoint} suffix (shadow-lg@md, hover:shadow-xl@lg, shadow-none@md). Inset shadows, active: shadows and elevation classes have none.
<div class="p-4"> <div class="shadow-none shadow-lg@md p-4 bg-surface rounded">shadow-none shadow-lg@md: resize the window</div></div>.panel { @include apply(shadow(lg) md);}| Suffix | Media query |
|---|---|
@xs | @media (min-width: 480px) |
@sm | @media (min-width: 640px) |
@md | @media (min-width: 768px) |
@lg | @media (min-width: 1024px) |
@xl | @media (min-width: 1280px) |
@2xl | @media (min-width: 1536px) |
@use "@nuvoui/core" as *;
.card { @include apply(shadow(sm), shadow(lg) hover, shadow(md, dark) lg);}.card { box-shadow: var(--nu-shadow-sm);}.card:hover { box-shadow: var(--nu-shadow-lg);}@media (min-width: 1024px) { .card { box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.2); }}| Mixin | Arguments |
|---|---|
shadow($size: md, $color: default) |
A size and a colour from the config maps; the default colour outputs the token |
shadow-inset($size: md, $color: default) |
As shadow(), inset, always a plain value |
elevation($level) |
1 to 5 |
shadow-base($x, $y, $blur, $spread, $color) |
Any values, for a one-off shadow |
shadow() accepts configured keys only. For a shadow outside the scale, use shadow-base() or write box-shadow yourself.
.floating-button { @include shadow-base(0, 2px, 8px, 0, rgb(0 0 0 / 25%));}Tokens in your own CSS
Section titled “Tokens in your own CSS”.popover { box-shadow: token(shadow-lg);}Configuration
Section titled “Configuration”$shadow-sizes maps each size to its x, y, blur and spread; $shadow-colors maps each colour name to a colour. Each replaces the default map; it is not merged. Keep the default colour, and keep sm, md, lg, xl and monster, which the elevation classes use.
@use "@nuvoui/core" with ( $generate-utility-classes: true, $shadow-colors: ("default": rgb(15 23 42 / 12%), "dark": rgb(15 23 42 / 24%), "darker": rgb(15 23 42 / 36%), "darkest": rgb(15 23 42 / 50%)));Changing a colour changes every token and class that uses it: here the tokens become 0 1px 2px 0 rgba(15, 23, 42, 0.12) and so on.
Errors
Section titled “Errors”| Input | Error |
|---|---|
shadow(huge) |
Shadow size 'huge' not found in config-shadow.$shadow-sizes map |
shadow(md, red) |
Shadow color 'red' not found in config-shadow.$shadow-colors map |
elevation(6) |
elevation(6): use a level from 1 to 5. |
CSS variables
Section titled “CSS variables”| Variable | Description |
|---|---|
--nu-shadow-{size} |
One token per key of $shadow-sizes, in the default colour. Fixed across themes. See Scale. |
Class reference
Section titled “Class reference”Generated from the compiled core build. Responsive and state variants are not listed.
Shadow (7 rules)
| Selector | Declarations | Default value |
|---|---|---|
.shadow-sm | box-shadow: var(--nu-shadow-sm) | 0 1px 2px 0 rgba(0, 0, 0, 0.1) |
.shadow-md | box-shadow: var(--nu-shadow-md) | 0 4px 6px -1px rgba(0, 0, 0, 0.1) |
.shadow-lg | box-shadow: var(--nu-shadow-lg) | 0 10px 15px -3px rgba(0, 0, 0, 0.1) |
.shadow-xl | box-shadow: var(--nu-shadow-xl) | 0 20px 25px -5px rgba(0, 0, 0, 0.1) |
.shadow-hero | box-shadow: var(--nu-shadow-hero) | 0 20px 25px 5px rgba(0, 0, 0, 0.1) |
.shadow-monster | box-shadow: var(--nu-shadow-monster) | 0 20px 55px 20px rgba(0, 0, 0, 0.1) |
.shadow-none | box-shadow: none | none |
Shadow colours (18 rules)
| Selector | Declarations | Default value |
|---|---|---|
.shadow-sm-dark | box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.2) | 0 1px 2px 0 rgba(0, 0, 0, 0.2) |
.shadow-sm-darker | box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.35) | 0 1px 2px 0 rgba(0, 0, 0, 0.35) |
.shadow-sm-darkest | box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.5) | 0 1px 2px 0 rgba(0, 0, 0, 0.5) |
.shadow-md-dark | box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.2) | 0 4px 6px -1px rgba(0, 0, 0, 0.2) |
.shadow-md-darker | box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.35) | 0 4px 6px -1px rgba(0, 0, 0, 0.35) |
.shadow-md-darkest | box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.5) | 0 4px 6px -1px rgba(0, 0, 0, 0.5) |
.shadow-lg-dark | box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.2) | 0 10px 15px -3px rgba(0, 0, 0, 0.2) |
.shadow-lg-darker | box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.35) | 0 10px 15px -3px rgba(0, 0, 0, 0.35) |
.shadow-lg-darkest | box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.5) | 0 10px 15px -3px rgba(0, 0, 0, 0.5) |
.shadow-xl-dark | box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.2) | 0 20px 25px -5px rgba(0, 0, 0, 0.2) |
.shadow-xl-darker | box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.35) | 0 20px 25px -5px rgba(0, 0, 0, 0.35) |
.shadow-xl-darkest | box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.5) | 0 20px 25px -5px rgba(0, 0, 0, 0.5) |
.shadow-hero-dark | box-shadow: 0 20px 25px 5px rgba(0, 0, 0, 0.2) | 0 20px 25px 5px rgba(0, 0, 0, 0.2) |
.shadow-hero-darker | box-shadow: 0 20px 25px 5px rgba(0, 0, 0, 0.35) | 0 20px 25px 5px rgba(0, 0, 0, 0.35) |
.shadow-hero-darkest | box-shadow: 0 20px 25px 5px rgba(0, 0, 0, 0.5) | 0 20px 25px 5px rgba(0, 0, 0, 0.5) |
.shadow-monster-dark | box-shadow: 0 20px 55px 20px rgba(0, 0, 0, 0.2) | 0 20px 55px 20px rgba(0, 0, 0, 0.2) |
.shadow-monster-darker | box-shadow: 0 20px 55px 20px rgba(0, 0, 0, 0.35) | 0 20px 55px 20px rgba(0, 0, 0, 0.35) |
.shadow-monster-darkest | box-shadow: 0 20px 55px 20px rgba(0, 0, 0, 0.5) | 0 20px 55px 20px rgba(0, 0, 0, 0.5) |
Inset shadow (24 rules)
| Selector | Declarations | Default value |
|---|---|---|
.shadow-inset-sm | box-shadow: inset 0 1px 2px 0 rgba(0, 0, 0, 0.1) | inset 0 1px 2px 0 rgba(0, 0, 0, 0.1) |
.shadow-inset-sm-dark | box-shadow: inset 0 1px 2px 0 rgba(0, 0, 0, 0.2) | inset 0 1px 2px 0 rgba(0, 0, 0, 0.2) |
.shadow-inset-sm-darker | box-shadow: inset 0 1px 2px 0 rgba(0, 0, 0, 0.35) | inset 0 1px 2px 0 rgba(0, 0, 0, 0.35) |
.shadow-inset-sm-darkest | box-shadow: inset 0 1px 2px 0 rgba(0, 0, 0, 0.5) | inset 0 1px 2px 0 rgba(0, 0, 0, 0.5) |
.shadow-inset-md | box-shadow: inset 0 4px 6px -1px rgba(0, 0, 0, 0.1) | inset 0 4px 6px -1px rgba(0, 0, 0, 0.1) |
.shadow-inset-md-dark | box-shadow: inset 0 4px 6px -1px rgba(0, 0, 0, 0.2) | inset 0 4px 6px -1px rgba(0, 0, 0, 0.2) |
.shadow-inset-md-darker | box-shadow: inset 0 4px 6px -1px rgba(0, 0, 0, 0.35) | inset 0 4px 6px -1px rgba(0, 0, 0, 0.35) |
.shadow-inset-md-darkest | box-shadow: inset 0 4px 6px -1px rgba(0, 0, 0, 0.5) | inset 0 4px 6px -1px rgba(0, 0, 0, 0.5) |
.shadow-inset-lg | box-shadow: inset 0 10px 15px -3px rgba(0, 0, 0, 0.1) | inset 0 10px 15px -3px rgba(0, 0, 0, 0.1) |
.shadow-inset-lg-dark | box-shadow: inset 0 10px 15px -3px rgba(0, 0, 0, 0.2) | inset 0 10px 15px -3px rgba(0, 0, 0, 0.2) |
.shadow-inset-lg-darker | box-shadow: inset 0 10px 15px -3px rgba(0, 0, 0, 0.35) | inset 0 10px 15px -3px rgba(0, 0, 0, 0.35) |
.shadow-inset-lg-darkest | box-shadow: inset 0 10px 15px -3px rgba(0, 0, 0, 0.5) | inset 0 10px 15px -3px rgba(0, 0, 0, 0.5) |
.shadow-inset-xl | box-shadow: inset 0 20px 25px -5px rgba(0, 0, 0, 0.1) | inset 0 20px 25px -5px rgba(0, 0, 0, 0.1) |
.shadow-inset-xl-dark | box-shadow: inset 0 20px 25px -5px rgba(0, 0, 0, 0.2) | inset 0 20px 25px -5px rgba(0, 0, 0, 0.2) |
.shadow-inset-xl-darker | box-shadow: inset 0 20px 25px -5px rgba(0, 0, 0, 0.35) | inset 0 20px 25px -5px rgba(0, 0, 0, 0.35) |
.shadow-inset-xl-darkest | box-shadow: inset 0 20px 25px -5px rgba(0, 0, 0, 0.5) | inset 0 20px 25px -5px rgba(0, 0, 0, 0.5) |
.shadow-inset-hero | box-shadow: inset 0 20px 25px 5px rgba(0, 0, 0, 0.1) | inset 0 20px 25px 5px rgba(0, 0, 0, 0.1) |
.shadow-inset-hero-dark | box-shadow: inset 0 20px 25px 5px rgba(0, 0, 0, 0.2) | inset 0 20px 25px 5px rgba(0, 0, 0, 0.2) |
.shadow-inset-hero-darker | box-shadow: inset 0 20px 25px 5px rgba(0, 0, 0, 0.35) | inset 0 20px 25px 5px rgba(0, 0, 0, 0.35) |
.shadow-inset-hero-darkest | box-shadow: inset 0 20px 25px 5px rgba(0, 0, 0, 0.5) | inset 0 20px 25px 5px rgba(0, 0, 0, 0.5) |
.shadow-inset-monster | box-shadow: inset 0 20px 55px 20px rgba(0, 0, 0, 0.1) | inset 0 20px 55px 20px rgba(0, 0, 0, 0.1) |
.shadow-inset-monster-dark | box-shadow: inset 0 20px 55px 20px rgba(0, 0, 0, 0.2) | inset 0 20px 55px 20px rgba(0, 0, 0, 0.2) |
.shadow-inset-monster-darker | box-shadow: inset 0 20px 55px 20px rgba(0, 0, 0, 0.35) | inset 0 20px 55px 20px rgba(0, 0, 0, 0.35) |
.shadow-inset-monster-darkest | box-shadow: inset 0 20px 55px 20px rgba(0, 0, 0, 0.5) | inset 0 20px 55px 20px rgba(0, 0, 0, 0.5) |
Elevation (5 rules)
| Selector | Declarations | Default value |
|---|---|---|
.elevation-1 | box-shadow: var(--nu-shadow-sm)z-index: 1 | 0 1px 2px 0 rgba(0, 0, 0, 0.1)1 |
.elevation-2 | box-shadow: var(--nu-shadow-md)z-index: 2 | 0 4px 6px -1px rgba(0, 0, 0, 0.1)2 |
.elevation-3 | box-shadow: var(--nu-shadow-lg)z-index: 3 | 0 10px 15px -3px rgba(0, 0, 0, 0.1)3 |
.elevation-4 | box-shadow: var(--nu-shadow-xl)z-index: 4 | 0 20px 25px -5px rgba(0, 0, 0, 0.1)4 |
.elevation-5 | box-shadow: var(--nu-shadow-monster)z-index: 5 | 0 20px 55px 20px rgba(0, 0, 0, 0.1)5 |
Accessibility and RTL
Section titled “Accessibility and RTL”- Shadows are decoration. A shadow alone does not tell a screen reader or a high contrast user that something is raised or selected. Use a border, a heading or ARIA state for meaning.
- Forced colours. Windows High Contrast mode removes box shadows. If a card’s edge matters, add a border (a
border-transparentborder becomes visible in forced colours). - Focus.
focus:shadow-*is not a focus indicator on its own; keep the outline focus ring (WCAG 2.4.7 Focus Visible). - RTL. The default shadows have no horizontal offset, so they look the same in both directions. A custom shadow with an x offset does not mirror in right-to-left layouts.