Which ways can you write a colour in CSS?
Every colour property, such as color, background-color and border-color, accepts the same set of colour values. You can write a keyword like tomato, a hex code like #e4572e, or a function like rgb(), hsl() or oklch(). The browser converts all of them to the same internal form, so picking a notation is about how easy the value is to read and adjust.
Named colours are handy for quick tests and teaching. There are about 150 of them, from black and white to rebeccapurple and papayawhip. They cover a random spread of hues, so real designs rarely stick to them. Hex is the most common format in design tools and older code. The functions are better when you want to tweak a colour by hand, because their numbers mean something you can reason about.
When you inspect an element in DevTools, the Styles panel shows a small swatch next to each colour. Click it to open a picker that can convert between formats. Shift+click the swatch to cycle through hex, rgb and hsl.
.price {
color: darkgreen;
}
.sale {
color: #c2410c;
}
.sold-out {
color: rgb(120 120 120);
}

7a 3e 00 reads as red 122, green 62, blue 0. The rows on the right are the same burnt orange in other notations; pick the one that is easiest to adjust.How do hex codes work?
A hex colour is three pairs of base-16 digits: red, green and blue, each from 00 (none) to ff (full). #ff0000 is pure red, #000000 is black and #ffffff is white. Equal pairs give a grey, so #777777 is a mid grey.
The three-digit form doubles each digit, so #f80 means #ff8800. Add a fourth or eighth digit and you get an alpha channel for transparency: #00000080 is black at about 50% opacity, and #0008 is the short form of the same thing. Any other length, such as five or seven digits, is invalid. The browser throws the whole declaration away without an error, so a typo in a hex code shows up as a missing colour.
Hex is compact and easy to copy from a design file. It is hard to adjust by eye: making #7a3e00 a little lighter means changing all three pairs in step, which is where the functional notations help.
When should you use rgb() and hsl()?
The rgb() function takes the same three channels as hex, written as decimal numbers from 0 to 255. Modern CSS separates them with spaces and puts an optional alpha value after a slash: rgb(40 90 60 / 0.8). The older comma form rgb(40, 90, 60) and the separate rgba() function still work everywhere. Mixing the two styles, for example spaces plus a comma before the alpha, is invalid.
The hsl() function describes a colour as hue, saturation and lightness. Hue is an angle on the colour wheel, where 0 is red, 120 is green and 240 is blue. Saturation and lightness are percentages. This makes small changes readable: hsl(25 80% 40%) is a burnt orange, and hsl(25 80% 55%) is a lighter version of the same orange.
The weakness of hsl() is that its lightness number does not match what your eyes see. Yellow at 50% lightness looks far brighter than blue at 50%. If you build a palette by keeping lightness fixed and rotating the hue, some colours will look washed out and others dark.
.badge {
background-color: hsl(25 80% 92%);
color: hsl(25 80% 30%);
border: 1px solid rgb(40 90 60 / 0.5);
}Why does oklch() give more even colours?
The oklch() function uses a colour space built to match human perception. It takes lightness (0% to 100%), chroma (how vivid, roughly 0 to 0.37) and hue (an angle). Two colours with the same lightness in oklch() look equally light, whatever their hue. That makes palettes, hover states and dark themes much more predictable.
A practical use: pick one lightness for all your button backgrounds, say 55%, and vary only the hue. The green, blue and red buttons will look equally heavy on the page, and the same white text will have similar contrast on all three. Try the same trick with hsl() and the yellow button will look almost white.
The oklch() function also reaches colours outside the older sRGB range on wide-gamut screens, such as most recent phones and laptops. If a value is outside what the screen can show, the browser maps it to the nearest colour it can display. Supported in all current browsers since 2023.
.btn-save {
background-color: oklch(55% 0.15 150);
}
.btn-delete {
background-color: oklch(55% 0.15 25);
}
.btn-info {
background-color: oklch(55% 0.15 250);
}What do currentColor and transparent do?
The keyword currentColor means the element's own computed color value. Borders, outlines and SVG icons often use it so they match the text without repeating the value. Change color on a link's hover state and an icon set to fill: currentColor follows along. Borders already default to currentColor when you leave the colour out, which is why border: 1px solid draws a line in the text colour.
The keyword transparent is a fully see-through black, the same as rgb(0 0 0 / 0). It is the default background of every element. You use it to hide a border while keeping its width, so a hover border can appear without the layout shifting by two pixels.
Opacity on a colour and the opacity property are different tools. An alpha value on background-color makes only the background see-through. The opacity property fades the whole element, including its text and children, which often makes text unreadable.
.tag {
color: #1f6f5c;
border: 2px solid currentColor;
}
.tab {
border-bottom: 3px solid transparent;
}
.tab:hover {
border-bottom-color: currentColor;
}How much contrast does text need?
WCAG, the accessibility guideline most laws point to, asks for a contrast ratio of at least 4.5:1 between body text and its background. Large text, meaning about 24px regular or 18.66px bold and above, needs 3:1. Icons and input borders that carry meaning also need 3:1 against what surrounds them.
The ratio compares relative luminance. Black on white is 21:1, the maximum. Mid grey #767676 on white is right at 4.5:1, so anything paler fails for body text. Pale grey placeholder text and light brown on cream are the usual failures, and they are what the demo starts with.
Chrome and Edge DevTools show the ratio when you click a colour swatch for a color declaration, with tick marks for AA and AAA. Firefox has the same check in its Accessibility panel. Check text that sits on images too, because the ratio changes across the photo.
When an AI tool writes CSS for you, look for grey text such as #999 or #aaa on white, placeholder colours, and text set with opacity: 0.6. They look tidy in a screenshot and fail contrast checks.
How do color-scheme and light-dark() help with dark mode?
The color-scheme property tells the browser which themes your page supports. Setting color-scheme: light dark on :root lets form controls, scrollbars and the default page background switch to dark versions when the user's system is in dark mode. Without it, a dark page still shows bright white inputs.
The light-dark() function takes two colours and returns the first in light mode and the second in dark mode. It only works when color-scheme allows both. It keeps each pair of colours on one line, which is easier to maintain than a separate media query for every colour. Supported in all current browsers since 2024.
For bigger themes, most teams combine this with custom properties so the values live in one place. The next lesson covers that approach.
:root {
color-scheme: light dark;
}
body {
background-color: light-dark(#fffaf0, #1c1917);
color: light-dark(#292524, #e7e5e4);
}How should you pick a notation for a real project?
Keep one notation per project so colours are easy to compare in code review. Hex works fine when values come straight from a design file and rarely change. Use oklch() when you build your own palette, need hover and pressed shades, or want a dark theme with the same visual weight. Use rgb() with a slash when you need a quick transparent overlay of an existing colour.
Store each colour once, ideally as a custom property, and refer to it by role, such as --text and --surface, rather than by appearance like --brown. When the brand changes from brown to green, a name like --brown becomes a lie in every file that uses it.
Field notes
- Every colour property accepts the same kinds of colour values.
- Hex codes use three pairs of digits for red, green and blue.
rgb()andhsl()can take a fourth value for transparency.- Body text needs a contrast ratio of at least 4.5:1 against its background.
Easy to get wrong
Hex colours must have 3, 4, 6 or 8 digits. Any other length is invalid and the browser silently drops the declaration.
Space-separated rgb() puts alpha after a slash. A comma there mixes two syntaxes and makes the whole value invalid.
Light grey on white falls well below 4.5:1, so many readers cannot read it. Check the ratio in DevTools before shipping pale text.
The opacity property fades the text and children along with the background. An alpha channel on the background colour fades only the background.
Questions people ask
Is hex or rgb() faster for the browser?
Neither. The browser parses both into the same internal value once, so the choice only affects how readable your code is.
Can I use oklch() in production today?
Yes. Every current browser has supported it since 2023. If you must support very old browsers, put a hex fallback declaration on the line above and the browser keeps the last one it understands.
What does chroma mean in oklch()?
Chroma is how vivid the colour is. Zero is a grey, and higher numbers are more saturated. Values above about 0.2 may be outside what many screens can show, and the browser maps them to the closest displayable colour.
Does light-dark() replace prefers-color-scheme media queries?
For single colour values it often can. You still need the media query when dark mode changes something other than a colour, such as an image, a shadow strength or a border width.
Why is my border the same colour as my text?
If you leave the colour out of a border declaration, it uses currentColor, which is the element's text colour. Add a colour to the shorthand to change it.