Styling

A terminal cell holds one character, a foreground color, a background color, and a handful of attributes. Every CSS property either resolves to one of those or has no effect. This guide is the mapping. The compatibility matrix lists every property with a yes or a no; this is the why and the how.

Units #

1px and 1ch are one cell. Which axis is meant comes from the property: width: 10px is ten columns and height: 10px is ten rows. em, rem, and ex scale from font-size, which is 1 by default, so 1em is one cell too. Percentages, vw, vh, and calc() work. A length that lands between cells resolves to a whole cell.

font-size and font-family have no effect. A terminal draws one font at one size.

Colors #

color and background-color take named colors, hex in three, four, six, or eight digits, and rgb() and hsl() in both the comma form and the modern space-separated form with / alpha. The color is sent at the depth the terminal has:

DepthSent as
rgbThe exact color, as 24-bit SGR
256The nearest of the 256-color palette: the 6ร—6ร—6 cube, or the gray ramp for grays
ansiThe nearest of the eight basic colors, by whether each channel is above half

The process transport picks the depth from COLORTERM and TERM. A transport of your own sets it directly.

A cell has no alpha. A color with alpha 0 paints nothing, and any other alpha paints the color as if opaque, except in a gradient, where stops composite over the color beneath them.

The system colors mean what they mean in a terminal:

ColorMeaning
CanvasThe terminal's own background. A box with background-color: Canvas clears to it, which is how a dialog or popover covers what is behind it.
CanvasTextThe terminal's own foreground.
Highlight, HighlightTextInverse video. The pair is what ::selection uses.
SelectedItem, SelectedItemTextInverse video too.
LinkTextBlue, the color of <a href>.
GrayTextGray.

A background-image with a linear-gradient() paints each cell the color the gradient has at that cell's center. radial-gradient() and url() have no effect. opacity, filter, box-shadow, and mix-blend-mode have no effect either.

Text attributes #

PropertyTerminal attribute
font-weight at 600 or more, bold, bolderBold
font-weight at 300 or less, lighterDim
font-style: italicItalic
text-decoration-line: underlineUnderline
text-decoration-line: overlineOverline, on a terminal that draws one
text-decoration-line: line-throughStrikethrough
text-decoration-style: doubleDouble underline, on a terminal that draws one; a single underline elsewhere
text-transformuppercase, lowercase, and capitalize change the characters at paint time

text-decoration-color, text-decoration-thickness, letter-spacing, word-spacing, and line-height have no effect. A line is one row.

Terminals differ in how they draw italic and dim, and some draw neither. A design that must read the same everywhere uses color.

Borders #

border takes one cell per side and draws with box-drawing characters. The style picks the glyph set:

StyleGlyphs
solid, and groove, ridge, inset, outsetโ”€ โ”‚ โ”Œ โ” โ”” โ”˜
doubleโ• โ•‘ โ•” โ•— โ•š โ•
dashedโ•Œ โ”†
dottedโ”„ โ”Š
hidden, noneNothing

The three shaded styles draw as solid because a terminal has one weight of line. Any border-radius above zero rounds the corners to โ•ญ โ•ฎ โ•ฐ โ•ฏ. A width above 1px is still one cell.

Borders that meet in a cell join. A rule that reaches a box's side ends in โ”œ or โ”ค, a column divider crossing it makes โ”ฌ or โ”ด, and two crossing rules make โ”ผ. Table borders collapse this way by default, and so do a fieldset and its legend.

outline draws in the ring a border would occupy when the box has a border, recoloring it. A box without a border gets an overline along its top row and an underline along its bottom, so the outline takes no space. outline-offset has no effect.

visibility: hidden leaves the box's space and paints nothing in it.

Generated content #

::before and ::after with a string content work; counter() in content is not implemented. ::marker styles a list item's bullet. list-style-type draws โ€ข, โ—ฆ, and โ–ช for disc, circle, and square, and the counting styles, decimal, lower-roman, upper-alpha, and the rest, draw a number followed by a dot. An <ol start> and an <li value> set the count as in a browser.

Pseudo-classes and pseudo-elements #

:hover matches the element under the mouse, and asking for it turns on mouse motion reporting for as long as a rule needs it. :focus and :focus-visible match the focused element. :fullscreen, :popover-open, :modal, :checked, :disabled, :placeholder-shown, :dir(), and the tree-structural pseudo-classes all match as in a browser.

::selection styles selected text, and ::highlight() styles the ranges in CSS.highlights. ::placeholder styles a text control's placeholder. ::backdrop styles what a modal dialog or popover covers. ::part() reaches into the built-in controls, which are shadow trees; the API guide lists the parts each has.

Default looks #

The user-agent stylesheet gives HTML elements their terminal looks. Any of them can be overridden by an ordinary rule.

ElementDefault
b, strong, th, legendBold
i, em, cite, dfn, varItalic
uUnderline
sStrikethrough
kbdBold and underlined
smallDim
codeA dark background
a[href]LinkText and underlined; inverse when focused
hrA horizontal rule, one row
prewhite-space: pre
fieldset, textarea, dialog, [popover]A solid border with 0 1ch padding
td, thA solid border with 0 1ch padding, collapsed with the table's
buttonThe label between [ and ]
summaryโ–ธ closed and โ–พ open
progress, meterA 10ch bar, with a groove part and a bar part
input, selectInline, white-space: pre
input[type=checkbox], input[type=radio]3ch wide, [x] or (x)
dialog:modal, [popover]Centered by position: fixed and auto margins, on a Canvas background
mathInline; display="block" centers it

Headings have no default size or weight, because there is no size. Style them:

h1 { font-weight: bold; text-decoration: underline; }
h2 { font-weight: bold; }

A focused element that does not edit text shows a solid outline. A focused text field or editing host shows the terminal cursor at its caret instead.

What terminals disagree on #

Some attributes depend on the terminal, and the engine finds out which by asking it rather than by its name:

Edit this page on GitHub