Colors
Color and type are most of what users actually perceive as design, and on a team they arrive as exact values, such as a brand gold of #d4af37. Reading and writing these formats fluently is part of the job.
Three common ways to write the same color:
h1 {
color: rgb(212, 175, 55); /* red, green, blue: 0 to 255 each */
color: #d4af37; /* hex: the same three numbers in base 16 */
color: gold; /* one of about 140 named colors */
}| Notation | Shape | Note |
|---|---|---|
rgb(212, 175, 55) | three 0 to 255 channels | most readable |
#d4af37 | two hex digits per channel | what you will see everywhere |
gold | a name | about 140 exist |
Hex is #RRGGBB, with two hex digits from 00 to ff, meaning 0 to 255, per channel. So #000000 is black, #ffffff is white, and #ff0000 is pure red.
Two properties use colors constantly. color sets the text color, and background-color, or the shorthand background, fills the area behind the element.
For transparency, rgba(0, 0, 0, 0.5) adds a fourth number, an opacity from 0 for invisible to 1 for solid.
Typography
body {
font-family: Georgia, "Times New Roman", serif;
font-size: 16px;
line-height: 1.5;
}| Property | Effect | Good default |
|---|---|---|
font-family | a fallback list of fonts to try | end with a generic family |
font-size | text size | 16px, the browser default |
line-height | space between lines, as a multiple | 1.4 to 1.6 |
font-weight | boldness | bold, or 100 to 900 |
text-align | horizontal alignment | left for body text |
font-family is a fallback list, so the browser tries Georgia, then Times New Roman, then whatever serif font it has. Always ending with a generic family, meaning serif, sans-serif, or monospace, guarantees something sensible.
Body text should rarely go below 16px, and line-height given as a plain multiple rather than a pixel value scales correctly when the font size changes.
Setting these once on body lets nearly every element inherit them. Inheritance is part of the cascade, which is next lesson.
Reading a hex color
#00ff00 is pure green.
The channels read red, then green, then blue, so 00 red, ff green, and 00 blue means all green and nothing else. ff is 255, the maximum for a channel.
| Hex | Channels | Color |
|---|---|---|
#00ff00 | 0, 255, 0 | pure green |
#ff0000 | 255, 0, 0 | pure red |
#ffffff | 255, 255, 255 | white |
#000000 | 0, 0, 0 | black |
Reading hex by splitting it into three pairs makes this mechanical rather than memorized. Equal values in all three channels always give a gray, which is a useful sanity check.
Converting one channel to hex
Hex colors are base-16 numbers, and JavaScript converts a channel with toString(16).
console.log((212).toString(16)); console.log((5).toString(16)); console.log((5).toString(16).padStart(2, "0"));
Output
d4 5 05
| Value | Raw hex | Padded |
|---|---|---|
| 212 | d4 | d4 |
| 5 | 5 | 05 |
Small values produce a single digit, which is why padding is required. A hex color needs exactly two digits per channel, so an unpadded 5 would shift every later digit and produce a completely different color.
Building a full hex color
rgbToHex(r, g, b) converts three 0 to 255 channel values into a #rrggbb string, using the toString(16) and padStart pair from above.
function rgbToHex(r, g, b) { const hex = (n) => n.toString(16).padStart(2, "0"); return "#" + hex(r) + hex(g) + hex(b); } console.log(rgbToHex(212, 175, 55)); console.log(rgbToHex(0, 0, 0)); console.log(rgbToHex(255, 255, 255));
Output
#d4af37
#000000
#ffffff| Input | Output |
|---|---|
212, 175, 55 | #d4af37 |
0, 0, 0 | #000000 |
255, 255, 255 | #ffffff |
The one-channel helper does all of the real work, and the outer function only concatenates its three results after a #. Extracting that helper is what keeps the padding rule in exactly one place, so the black case comes out as six zeros rather than three.