Lesson 4 of 25

CSS Colors

Four Ways to Write the Same Colour

A screen makes every colour by mixing red, green and blue light. Almost every colour value in CSS is therefore the same three numbers wearing different clothes, and choosing between the notations is about which one is easiest for you to read and adjust, not about what the browser can display.

Named colours are keywords like red, tomato and rebeccapurple. There is a fixed list of them and no way to add your own. They are handy while sketching — background: red instantly shows you where an element really is — but almost none of them belong in a finished design, because they were never chosen to sit together.

Hex is the format you will see most often. #d1039e is nothing more mysterious than red, green and blue written in base 16, two digits each, from 00 to ff. rgb() writes the same three numbers in ordinary base 10, which is easier to nudge by hand. hsl() takes a completely different approach and describes a colour the way a person would — which shade, how vivid, how light — and that turns out to be far more useful than it first sounds.

Example
/* All four of these are the same pink */
.a { color: #d1039e; }
.b { color: rgb(209, 3, 158); }
.c { color: hsl(315, 97%, 42%); }

/* Named colours: useful for debugging, rarely for design */
.debug { outline: 2px solid red; }

/* Three-digit hex is shorthand for six, each digit doubled */
.d { color: #f0c; }        /* identical to #ff00cc */
.e { background: #fff; }   /* identical to #ffffff */

/* Two special keywords */
.f { background: transparent; }
.g { border: 1px solid currentColor; }   /* matches this element's text colour */
  • Named — red, white, tomato; fixed list, no custom names
  • Hex — #rrggbb, each pair from 00 to ff; #rgb is a shorthand form
  • RGB — rgb(209, 3, 158), each channel from 0 to 255
  • HSL — hsl(315, 97%, 42%): hue in degrees, then saturation and lightness as percentages
  • currentColor — reuses whatever color the element has, so borders and icons follow the text

HSL: the Format You Can Actually Reason About

Try to make #d1039e ten per cent darker by editing the hex digits. You cannot, not reliably — you would have to convert to decimal, scale three numbers and convert back. Now try the same with hsl(315, 97%, 42%): change the last number to 32% and you are done. That single property is why HSL is worth learning even if you paste hex codes from a design tool.

The three parts are straightforward. Hue is an angle around a colour wheel from 0 to 360: roughly 0 is red, 120 is green, 240 is blue, and back to red at 360. Saturation runs from 0%, which is pure grey, to 100%, which is as vivid as the screen allows. Lightness runs from 0%, which is black regardless of the other two values, to 100%, which is white — with 50% being the pure colour.

This gives you a practical way to build a palette without any design training. Pick one hue for your brand. Keep the hue and saturation fixed, and vary only the lightness to produce a dark shade for hover states, a mid shade for buttons and a very light shade for backgrounds. Everything stays visibly related because it is literally the same colour at different brightnesses. For an accent that contrasts, add or subtract about 180 from the hue to land on the opposite side of the wheel.

Example
/* One hue, one saturation, four lightness values = a coherent set */
:root {
  --brand-900: hsl(315, 97%, 22%);   /* darkest — text on light backgrounds */
  --brand-700: hsl(315, 97%, 32%);   /* hover state */
  --brand-500: hsl(315, 97%, 42%);   /* buttons, links */
  --brand-100: hsl(315, 97%, 95%);   /* tinted background */
}

.button        { background: var(--brand-500); color: #ffffff; }
.button:hover  { background: var(--brand-700); }
.notice        { background: var(--brand-100); color: var(--brand-900); }

/* Lightness beats everything else */
hsl(315, 97%, 0%)     /* black, whatever the hue */
hsl(315, 97%, 100%)   /* white, whatever the hue */
hsl(315, 0%, 42%)     /* grey — saturation 0 removes the colour */
Notes
  • Design tools and colour pickers usually show HSL alongside hex, so you can copy the hue and saturation from a colour you already like and build the rest of the scale yourself by changing only the lightness.

Transparency: rgba() Versus opacity

There are two ways to make something see-through, they are not interchangeable, and mixing them up produces one of the most common bugs in beginner CSS.

The first way is a transparent colour. rgba(0, 0, 0, 0.5) is black at half strength, and hsla() works the same way; you can also append two extra digits to a hex value, so #000000 at half strength is #00000080. Because the transparency lives in the colour, it affects only the property you used it on. A card with background: rgba(255, 255, 255, 0.8) has a slightly see-through background, and its text stays perfectly solid.

The second way is the opacity property, and this one applies to the element and everything inside it. Set opacity: 0.5 on a card and the background fades, but so does the heading, the body text, the button and any image — you cannot make a child fully opaque again, because opacity flattens the whole subtree together. If your faded text is the problem, you almost certainly wanted a transparent background colour instead. Reach for opacity deliberately, when you genuinely want the entire element to fade, such as a disabled button or an element being animated in.

Example
/* Transparency in the colour — affects only that property */
.overlay-card {
  background: rgba(255, 255, 255, 0.85);   /* background is see-through */
  color: #222222;                          /* text stays solid */
}

/* Same idea in hex — the last two digits are the alpha channel */
.tint { background: #d1039e33; }

/* opacity — affects the element and every child */
.disabled-card {
  opacity: 0.5;   /* heading, text, button: all faded, no exceptions */
}

/* A very common real use: a readable caption over a photo */
.photo-caption {
  background: rgba(0, 0, 0, 0.6);
  color: #ffffff;
  padding: 12px 16px;
}
Notes
  • opacity has a second, quieter effect: any value below 1 creates a new stacking context, which changes how z-index behaves inside that element. If a dropdown suddenly refuses to appear above its neighbours, check whether an ancestor has an opacity below 1. The lesson on positioning explains why.

Colour That Everyone Can Actually Read

Light grey text on a white background looks elegant on a bright laptop screen in a quiet room. On a phone in afternoon sunlight, or to a reader with weaker eyesight, it can be genuinely unreadable. Contrast is not a finishing touch — it decides whether your content reaches people at all.

The accessibility guidelines most of the web follows set a measurable target: normal body text should have a contrast ratio of at least 4.5 to 1 against its background, and large text — roughly 24px, or 19px when bold — at least 3 to 1. You do not calculate this by hand. Browser developer tools show the ratio when you inspect a colour, and free contrast checkers do the same from two hex codes. Checking takes seconds and removes the guesswork.

The second habit matters just as much: never let colour be the only thing carrying a message. A form field that turns red and says nothing else is invisible to a colour-blind user and to anyone using a screen reader. Add an icon, a border change, or — best of all — words. "Enter a valid mobile number" beside a red border tells everybody the same thing.

One last property worth knowing: color is inherited. Set it once on body and every element inside starts from that value unless it sets its own. That is why a sensible dark grey on body plus a handful of exceptions is usually all the text colour a site needs.

  • Aim for at least 4.5:1 contrast for body text, 3:1 for large text
  • Check with the contrast readout in developer tools rather than by eye
  • Never signal errors, required fields or status with colour alone
  • color inherits; background-color does not
  • Pure black text on pure white is harsher than it needs to be — a very dark grey such as #222222 reads more comfortably
  • Define your colours once as custom properties so a palette change is a single edit
Notes
  • Test your page in your operating system's dark mode too. A design that only sets color and leaves background-color to the browser can end up as dark grey text on a dark background. Setting both together on body avoids the whole class of problem.
Ask AI