CSS background-color: every format, with live examples

One property paints the area behind an element. The value can be a name, a hex code, rgb() or hsl(), with or without transparency, and a few rules decide where the paint stops.

To set a background colour in CSS, give the element a background-color:

.card { background-color: #2563eb; }

The colour fills the element's content and padding, and runs under its border. It never fills the margin. The default value is transparent, and the property is not inherited: a child shows its parent's colour only because its own background is transparent.

Pick a colour and an alpha value below. The same colour is written three ways, and every line is valid CSS you can copy.

Live exampletry it here, then copy the code
Share it as a link
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>One colour, three ways to write it</title>
<style>
  body { margin: 0; padding: 16px; font-family: system-ui, sans-serif; background: #f4f5f7; color: #1d2330; }
  .controls { display: flex; flex-wrap: wrap; gap: 14px; align-items: center; margin-bottom: 14px; }
  label { font-size: 14px; display: flex; align-items: center; gap: 8px; }
  input[type=color] { width: 48px; height: 36px; border: 0; padding: 0; background: none; }
  input[type=range] { width: 150px; }
  /* checkerboard so you can see through a see-through colour */
  .stage {
    border-radius: 12px; padding: 14px;
    background-color: #fff;
    background-image: linear-gradient(45deg, #d7dbe2 25%, transparent 25%, transparent 75%, #d7dbe2 75%),
                      linear-gradient(45deg, #d7dbe2 25%, transparent 25%, transparent 75%, #d7dbe2 75%);
    background-size: 20px 20px; background-position: 0 0, 10px 10px;
  }
  .sample { border-radius: 10px; padding: 18px; background-color: #2563eb; }
  .sample p { margin: 0; font-size: 18px; font-weight: 600; }
  .sample .dark { color: #111827; }
  .sample .light { color: #ffffff; margin-top: 6px; }
  .codes { margin-top: 14px; display: grid; gap: 6px; }
  .codes div { display: flex; gap: 10px; align-items: baseline; font-size: 14px; }
  .codes b { width: 44px; flex: none; color: #6b7280; font-weight: 600; }
  code { font: 14px ui-monospace, Consolas, monospace; background: #fff; border: 1px solid #e1e4ea; border-radius: 6px; padding: 3px 7px; word-break: break-all; }
</style>
</head>
<body>
<div class="controls">
  <label>Colour <input type="color" id="pick" value="#2563eb"></label>
  <label>Alpha <input type="range" id="alpha" min="0" max="100" value="100"> <span id="pct">100%</span></label>
</div>

<div class="stage">
  <div class="sample" id="sample">
    <p class="dark">Dark text on this background</p>
    <p class="light">White text on this background</p>
  </div>
</div>

<div class="codes">
  <div><b>hex</b><code id="hex"></code></div>
  <div><b>rgb</b><code id="rgb"></code></div>
  <div><b>hsl</b><code id="hsl"></code></div>
</div>

<script>
  const pick = document.getElementById('pick');
  const alpha = document.getElementById('alpha');
  const sample = document.getElementById('sample');

  // #rrggbb -> [r, g, b] as 0-255
  const toRgb = (hex) => [1, 3, 5].map((i) => parseInt(hex.slice(i, i + 2), 16));

  // [r, g, b] -> [hue 0-360, saturation %, lightness %]
  function toHsl([r, g, b]) {
    r /= 255; g /= 255; b /= 255;
    const max = Math.max(r, g, b), min = Math.min(r, g, b), d = max - min;
    const l = (max + min) / 2;
    let h = 0, s = 0;
    if (d) {
      s = d / (1 - Math.abs(2 * l - 1));
      if (max === r) h = ((g - b) / d) % 6;
      else if (max === g) h = (b - r) / d + 2;
      else h = (r - g) / d + 4;
      h = (h * 60 + 360) % 360;
    }
    return [Math.round(h), Math.round(s * 100), Math.round(l * 100)];
  }

  function update() {
    const a = alpha.value / 100;
    const [r, g, b] = toRgb(pick.value);
    const [h, s, l] = toHsl([r, g, b]);
    const aHex = Math.round(a * 255).toString(16).padStart(2, '0');
    const slash = a < 1 ? ' / ' + a : '';  // alpha part only when it is not solid

    const css = {
      hex: a < 1 ? pick.value + aHex : pick.value,
      rgb: `rgb(${r} ${g} ${b}${slash})`,
      hsl: `hsl(${h} ${s}% ${l}%${slash})`,
    };
    for (const k in css) document.getElementById(k).textContent = 'background-color: ' + css[k] + ';';
    document.getElementById('pct').textContent = alpha.value + '%';
    sample.style.backgroundColor = css.rgb;  // hex and rgb are exact; hsl is rounded to whole numbers
  }

  pick.addEventListener('input', update);
  alpha.addEventListener('input', update);
  update();
</script>
</body>
</html>
One colour in hex, rgb and hsl. Drag the alpha slider to see the checkerboard show through.

The colour formats

All of these paint the same kind of fill. Pick the one that is easiest to read in your stylesheet.

Format Example Notes
Name tomato A fixed list, from aliceblue to yellowgreen
Hex, 6 digits #2563eb Red, green, blue in pairs
Hex, 3 digits #26e Short form of #2266ee
Hex, 8 digits #2563eb80 Last pair is alpha, 80 is about 50%
rgb() rgb(37 99 235) Each channel 0 to 255
rgb() with alpha rgb(37 99 235 / 0.5) Same as rgba(37, 99, 235, 0.5)
hsl() hsl(221 83% 53%) Hue, saturation, lightness
Keyword transparent No paint at all

The space-separated form with a slash is the modern syntax. The older comma form, rgba(37, 99, 235, 0.5), still works. Do not mix the two inside one function, or the whole declaration is dropped.

hsl() is the easiest to adjust by hand. Keep the hue and change only the lightness, and you get a lighter or darker shade of the same colour, which suits hover states and panels.

Transparent colours

An alpha value lets the parent show through. It fades only the fill, not the text on top, which is the usual reason to prefer it over opacity. CSS opacity shows the two side by side.

The catch is that a see-through colour has no fixed look. It mixes with whatever sits behind it.

The same rgb(... / 0.4) on three parents gives three colours. A solid hex looks the same on all of them.
The same rgb(... / 0.4) on three parents gives three colours. A solid hex looks the same on all of them.

background-color vs the background shorthand

background is a shorthand for the colour, image, position, size, repeat and a few more parts. Any part you leave out is reset to its default, and the default colour is transparent.

A background shorthand written after background-color wipes the colour.
A background shorthand written after background-color wipes the colour.

So this loses the navy:

.hero {
  background-color: navy;
  background: url(sun.png) no-repeat;  /* colour reset to transparent */
}

Either set the image with background-image, or put the colour in the shorthand: background: navy url(sun.png) no-repeat;. Gradients are images too, so the same rule applies to them. CSS gradient backgrounds cover those.

Where the background paints: padding, border, margin

By default the colour fills the border box. You only notice the part under the border when the border is dashed, dotted or see-through. background-clip changes where it stops.

Live exampletry it here, then copy the code
Share it as a link
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Where the background paints</title>
<style>
  body { margin: 0; padding: 16px; font-family: system-ui, sans-serif; background: #f4f5f7; color: #1d2330; }
  .opts { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 14px; }
  .opts label { font: 14px ui-monospace, Consolas, monospace; background: #fff; border: 1px solid #d5d9e0; border-radius: 8px; padding: 7px 10px; cursor: pointer; }
  .opts input { margin: 0 6px 0 0; }
  /* the parent shows the margin area as stripes */
  .parent {
    background: repeating-linear-gradient(45deg, #fff, #fff 6px, #eceef2 6px, #eceef2 12px);
    border: 1px solid #d5d9e0; border-radius: 10px; overflow: hidden;
  }
  .box {
    margin: 28px;                              /* never painted by .box */
    border: 12px dashed rgb(17 24 39 / 0.55);  /* dashed, so you can see under it */
    padding: 24px;
    background-color: #fbbf24;
    background-clip: border-box;               /* the default */
  }
  .content { background: rgb(255 255 255 / 0.7); outline: 1px dashed #6b7280; padding: 10px; font-size: 14px; text-align: center; }
  .legend { display: flex; flex-wrap: wrap; gap: 6px 16px; margin-top: 12px; font-size: 13px; color: #374151; }
  #out { margin-top: 10px; font: 14px ui-monospace, Consolas, monospace; }
</style>
</head>
<body>
<div class="opts" id="opts">
  <label><input type="radio" name="clip" value="border-box" checked>border-box</label>
  <label><input type="radio" name="clip" value="padding-box">padding-box</label>
  <label><input type="radio" name="clip" value="content-box">content-box</label>
</div>

<div class="parent">
  <div class="box" id="box"><div class="content">content</div></div>
</div>

<div class="legend">
  <span>Stripes = margin (never painted)</span>
  <span>Dashes = border</span>
  <span>Yellow = background-color</span>
</div>
<div id="out"></div>

<script>
  const box = document.getElementById('box');
  const out = document.getElementById('out');

  function show() {
    out.textContent = 'background-clip: ' + getComputedStyle(box).backgroundClip + ';';
  }

  document.getElementById('opts').addEventListener('change', (e) => {
    box.style.backgroundClip = e.target.value;  // where the colour stops
    show();
  });
  show();
</script>
</body>
</html>
Switch background-clip and watch the yellow stop at the border, padding or content edge. The striped margin is never painted.
  • border-box (default): under the border as well.
  • padding-box: stops at the inside edge of the border.
  • content-box: only behind the content, padding stays clear.

Margin is always outside the element's paint. To colour the gap between two cards, paint their parent.

A background colour for the whole page

The root element's background covers the whole browser canvas, not just the height of the content. When html has no background, the browser uses the body background for the canvas instead.

If html has its own colour, a body colour stops where the content stops. Put the page colour on html.
If html has its own colour, a body colour stops where the content stops. Put the page colour on html.
html { background-color: #f4f5f7; }
body { margin: 0; }

This is the simplest way to fill the window. Trouble starts when a reset or template sets html { background: white; } and you then colour only body.

Readable text on a coloured background

Every background colour needs a text colour that stands out from it. WCAG, the web accessibility guideline, asks for a contrast ratio of at least 4.5:1 for normal text and 3:1 for large text at level AA.

The theme demo below computes that ratio live. HTML font color has a table of safe text and background pairs.

Hover colours and themes with variables

Change the colour on :hover and add a short transition so the switch is smooth:

.cta { background-color: #2563eb; transition: background-color 0.2s; }
.cta:hover, .cta:focus-visible { background-color: #1d4ed8; }

For links, the order of the state rules matters. HTML link hover color walks through it.

When every background reads a CSS variable, one set of new values repaints the whole page. That is how theme switchers and dark mode work.

Live exampletry it here, then copy the code
Share it as a link
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Theme switcher with background-color variables</title>
<style>
  /* every background colour comes from one of these variables */
  :root {
    --page: #f4f5f7;
    --card: #ffffff;
    --text: #111827;
    --muted: #4b5563;
    --accent: #2563eb;
    --accent-hover: #1d4ed8;
    --on-accent: #ffffff;
  }
  html { background-color: var(--page); }  /* fills the whole frame */
  body { margin: 0; padding: 16px; font-family: system-ui, sans-serif; color: var(--text); }
  .themes { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 14px; }
  .themes button {
    font: 14px system-ui, sans-serif; padding: 8px 12px; border-radius: 8px; cursor: pointer;
    border: 1px solid var(--muted); background-color: var(--card); color: var(--text);
  }
  .themes button[aria-pressed="true"] { outline: 3px solid var(--accent); outline-offset: 1px; }
  .card { background-color: var(--card); border-radius: 12px; padding: 18px; box-shadow: 0 4px 14px rgb(0 0 0 / 0.08); }
  .card h2 { margin: 0 0 6px; font-size: 20px; }
  .card p { margin: 0 0 14px; color: var(--muted); line-height: 1.5; }
  .cta {
    font: 600 15px system-ui, sans-serif; border: 0; border-radius: 8px; padding: 10px 16px; cursor: pointer;
    background-color: var(--accent); color: var(--on-accent);
    transition: background-color 0.2s;
  }
  .cta:hover, .cta:focus-visible { background-color: var(--accent-hover); }
  .check { margin-top: 14px; font: 14px ui-monospace, Consolas, monospace; display: grid; gap: 4px; }
</style>
</head>
<body>
<div class="themes" id="themes">
  <button type="button" data-theme="light" aria-pressed="true">Light</button>
  <button type="button" data-theme="dark" aria-pressed="false">Dark</button>
  <button type="button" data-theme="sand" aria-pressed="false">Sand</button>
  <button type="button" data-theme="forest" aria-pressed="false">Forest</button>
</div>

<div class="card">
  <h2>Monthly report</h2>
  <p>Every background on this page reads a CSS variable. The buttons above change the variables, not the rules.</p>
  <button type="button" class="cta">Open the report</button>
</div>

<div class="check" id="check"></div>

<script>
  const themes = {
    light:  { page: '#f4f5f7', card: '#ffffff', text: '#111827', muted: '#4b5563', accent: '#2563eb', 'accent-hover': '#1d4ed8', 'on-accent': '#ffffff' },
    dark:   { page: '#0f172a', card: '#1e293b', text: '#f1f5f9', muted: '#cbd5e1', accent: '#60a5fa', 'accent-hover': '#93c5fd', 'on-accent': '#0f172a' },
    sand:   { page: '#f5efe3', card: '#fffaf0', text: '#3b2f1e', muted: '#6b5a41', accent: '#9a3412', 'accent-hover': '#7c2d12', 'on-accent': '#ffffff' },
    forest: { page: '#0b2e22', card: '#123d2e', text: '#ecfdf5', muted: '#bbf7d0', accent: '#34d399', 'accent-hover': '#6ee7b7', 'on-accent': '#052e1f' },
  };

  // contrast ratio between two #rrggbb colours (WCAG formula)
  function lum(hex) {
    const c = [1, 3, 5].map((i) => parseInt(hex.slice(i, i + 2), 16) / 255)
      .map((v) => (v <= 0.03928 ? v / 12.92 : ((v + 0.055) / 1.055) ** 2.4));
    return 0.2126 * c[0] + 0.7152 * c[1] + 0.0722 * c[2];
  }
  function ratio(a, b) {
    const [hi, lo] = [lum(a), lum(b)].sort((x, y) => y - x);
    return (hi + 0.05) / (lo + 0.05);
  }

  function apply(name) {
    const t = themes[name];
    for (const k in t) document.documentElement.style.setProperty('--' + k, t[k]);
    document.querySelectorAll('#themes button').forEach((b) =>
      b.setAttribute('aria-pressed', String(b.dataset.theme === name)));

    const rows = [['text on card', t.text, t.card], ['muted on card', t.muted, t.card], ['button text', t['on-accent'], t.accent]];
    document.getElementById('check').innerHTML = rows.map(([label, fg, bg]) => {
      const r = ratio(fg, bg);
      return `<div>${label}: ${r.toFixed(1)}:1 ${r >= 4.5 ? 'passes' : 'below'} 4.5:1</div>`;
    }).join('');
  }

  document.getElementById('themes').addEventListener('click', (e) => {
    const b = e.target.closest('button');
    if (b) apply(b.dataset.theme);
  });
  apply('light');
</script>
</body>
</html>
Four palettes, one set of rules. Each button changes the variables, and the contrast check updates.
:root { --page: #f4f5f7; --card: #ffffff; --accent: #2563eb; }
html  { background-color: var(--page); }
.card { background-color: var(--card); }

When it does not work

What you see Cause Fix
No colour at all on a box Its children are floated or absolutely positioned, so the box has no height display: flow-root for floats, or a min-height
The colour worked, then vanished A background shorthand later in the cascade reset it Use background-image, or put the colour in the shorthand
An rgba colour looks darker or lighter in one place Alpha mixes with the parent behind it Use a solid colour where it must match
Page colour stops halfway down html has its own background, so body paints only its own box Put the colour on html
The rule is ignored Invalid value, such as a 5-digit hex, none, or mixed commas and slash Use 3, 4, 6 or 8 hex digits, or transparent
Hover colour never shows A more specific rule sets the colour Match the selector's specificity in the :hover rule

Browser developer tools show a crossed-out declaration when it is invalid or overridden. That is the fastest way to tell the last three cases apart.

A screenshot of a colour scheme is a still picture. Nobody can hover the buttons, switch the themes or read the contrast check.

To send the live version, paste the page into a NOS document and choose Create share link. HTML to link walks through it.

The page renders as written and its scripts run, so the people you send it to can switch the themes themselves. If you change a colour later, the same link shows the new version.

Questions people ask

Is there a background-color: none?

No. none is not a valid colour, so background-color: none is ignored. Use background-color: transparent, which is also the default. The shorthand background: none is valid, and it resets the colour to transparent along with the image.

What is the difference between rgb() and rgba()?

In current CSS they are the same function. Both accept an alpha value, so rgb(0 0 0 / 0.5) and rgba(0, 0, 0, 0.5) give the same half see-through black. rgba() is still valid, so older code keeps working.

How do I change the background colour of the whole page?

Put it on html, or on body when html has no background of its own. The root element's background covers the whole browser canvas, so the colour fills the window even when the content is short.

How do I make only the background transparent, not the text?

Use a colour with alpha, such as rgb(255 255 255 / 0.6) or #ffffff99. The opacity property fades the text and children too. The CSS opacity guide compares the two side by side.

Where can I find a list of CSS colour names?

The CSS Color specification and MDN's named-color page list them all, from aliceblue to yellowgreen. Names are handy for quick tests. For a brand palette, hex or hsl values are easier to keep consistent.

Keep reading