Lesson 36 of 45 · Web pages

Responsive pages and states

A web page is opened on a phone, a laptop, a television and sometimes a printer, in light or dark mode, with a mouse, a finger or only the keyboard. This lesson shows how to write one page that adapts to all of them: fluid sizes, media queries, container queries, interaction states and the properties that control pointers, scrolling and printing.

One page, many screens

A phone is a small screen with a lot of pixels, and browsers hide that from you with the viewport: the visible area of the page, measured in CSS pixels. Without a hint, a phone pretends to be about 980 pixels wide and shrinks the page to fit. Salam writes the hint for you: every page it builds contains <meta name="viewport" content="width=device-width, initial-scale=1">, so the page is laid out at the real width of the screen.

That leaves one job to you: do not fix the size of things. A width = 800 is fine on a laptop and wider than the whole phone. Prefer sizes that bend. Four tools cover most of the need. max width sets a limit and lets the box shrink below it. A percentage is a share of the parent. The unit vw is one percent of the viewport width. And clamp(min, preferred, max) picks the preferred value but never goes below the minimum or above the maximum, which makes it ideal for font sizes and padding. min() and max() work the same way with a list of values, and width = "min(100%, 24rem)" means "as wide as the parent, but no more than 24rem".

web-resp-fluid.salam
page:
    title = "A fluid page"
    global: selector = "body" font family = "system-ui, sans-serif" margin = 0 line height = 1.6 end
    global: box sizing = "border-box" end
    box:
        max width = "40rem"
        margin = "0 auto"
        padding = "clamp(1rem, 4vw, 3rem)"
        heading:
            size = 1
            font size = "clamp(1.6rem, 6vw, 2.8rem)"
            content = "Fluid by default"
        end
        paragraph: content = "The box has a max width, so on a wide screen it stops growing. On a narrow one it shrinks with the screen." end
        box:
            width = "min(100%, 24rem)"
            background = "#dbeafe"
            border = "2px solid #1d4ed8"
            padding = 12
            content = "width: min(100%, 24rem)"
        end
    end
end

The line global: box sizing = "border-box" styles every element so that padding and borders count inside the width. Without it the box with max width = "40rem" would be wider than 40rem by its padding, and a box at width = "100%" with padding would overflow its parent. It is the first rule most stylesheets contain.

If your page scrolls sideways on a phone, something has a fixed width, or a long word or a wide image has no max width. Narrow the browser window to find it.

Media queries on one element

Fluid sizes are not enough when the layout itself has to change: three columns on a laptop, one column on a phone. For that CSS has media queries. In Salam the media element is a child of the element you want to change, and its condition attribute is the query in parentheses. The style properties you write inside media apply to the parent, and only while the condition is true.

web-resp-media.salam
page:
    title = "A media query on one element"
    global: selector = "body" font family = "system-ui, sans-serif" margin = 16 end
    global: selector = ".tile" background = "#dbeafe" border radius = 8 padding = 16 text align = "center" end
    box:
        display = grid
        grid template columns = "repeat(3, 1fr)"
        gap = 12
        background = "#f1f5f9"
        padding = 12
        media:
            condition = "(max-width: 480px)"
            grid template columns = "1fr"
            background = "#fef3c7"
        end
        box: class = "tile" content = "One" end
        box: class = "tile" content = "Two" end
        box: class = "tile" content = "Three" end
    end
    paragraph: content = "Three columns on a wide screen, one column and a yellow background under 480 pixels." end
end

The example reads: this grid has three equal columns and a grey background, and when the viewport is at most 480 pixels wide it has one column and a yellow background. Everything outside the media element is the normal case, and the media element lists only what is different.

Which direction to write the queries in is a matter of habit. Mobile first means the normal rules are for a phone and queries with min-width add the extra layout for larger screens. Desktop first does the opposite with max-width, as above. Mobile first usually means less code, because small screens need the simplest layout. A condition can combine tests with and, and a comma means "or": (min-width: 600px) and (max-width: 900px) is a range, and (orientation: landscape), (min-width: 1200px) is either one. Besides widths you can ask about orientation, the pointer ((pointer: coarse) is a finger), and whether the device can hover.

button:
    padding = "6 12"
    content = "Buy"
    media: condition = "(hover: none)" padding = "14 22" font size = 18 end
end

A touch screen has no hover, and a finger is less exact than a mouse, so the rule above makes the button bigger there. Test for the ability (hover: none, pointer: coarse) and not for the device name or the width: a tablet with a keyboard and a small laptop window are both real.

Queries for the whole page

A media element styles its parent. Rules for the whole page, such as colors for dark mode or what to hide on paper, are raw CSS and go into a style element, whose content is a CSS string. Most users choose dark mode in their system settings, and a page can read the choice with the prefers-color-scheme query. The usual way is to keep the colors in CSS custom properties, which are names that start with two hyphens and are read with var(), and to swap their values in the query. Then every rule that uses them follows without being written twice.

web-resp-dark.salam
page:
    title = "Light and dark"
    style:
        content = ":root { color-scheme: light dark; --bg: #ffffff; --fg: #111827; --card: #e5e7eb } @media (prefers-color-scheme: dark) { :root { --bg: #111827; --fg: #f9fafb; --card: #1f2937 } }"
    end
    global: selector = "body" font family = "system-ui, sans-serif" margin = 0 background = "var(--bg)" color = "var(--fg)" end
    box:
        padding = 24
        heading: size = 2 content = "Follows your system" end
        paragraph: content = "The colors are custom properties. A media query swaps them when the system is in dark mode." end
        box: background = "var(--card)" padding = 16 border radius = 8 content = "A card that uses the same variables" end
    end
end

The color scheme property, here color-scheme: light dark on :root, tells the browser that the page can do both. The browser then draws its own parts, form controls, scrollbars and the default page color, in the matching colors. Without it a dark page still has bright scrollbars and white inputs.

Other queries you will use are prefers-reduced-motion: reduce, for people who get ill from motion and who ask their system to cut it (the next lesson uses it), prefers-contrast: more, and print, for paper. Salam's style element also has a media attribute that wraps its CSS in a query: style: media = "print" content = "..." end.

A style element with a media attribute is written into the page before all other rules, so it loses against an equally specific rule that comes later. Use it for rules that only add or hide, and use !important in print rules for that reason. When you need to override a normal rule, as in dark mode, write the @media block inside the same content string, after the normal rule.

Container queries

A viewport query asks about the whole window, but a product card does not know where it will be placed. In a wide main column it can lay out its picture and text side by side, and in a narrow sidebar it must stack them, whatever the window is. A container query asks about the size of the nearest ancestor that was declared a container. container type = "inline-size" makes an element a container for queries about its width, container name gives it a name, and container sets both in the form name / type. The query is raw CSS, because it is a rule block like a media query: @container name (min-width: 22rem) { ... }.

web-resp-container.salam
page:
    title = "A container query"
    global: selector = "body" font family = "system-ui, sans-serif" margin = 16 end
    style:
        content = ".item { flex-direction: column } @container product (min-width: 22rem) { .item { flex-direction: row; align-items: center } }"
    end
    box:
        display = flex
        gap = 16
        align items = "flex-start"
        flex wrap = wrap
        box:
            width = 200
            container type = "inline-size"
            container name = "product"
            border = "1px dashed #94a3b8"
            box:
                class = "item"
                display = flex
                gap = 8
                padding = 8
                box: width = 48 height = 48 background = "#0f766e" border radius = 8 end
                paragraph: margin = 0 content = "A narrow container: stacked" end
            end
        end
        box:
            width = 380
            container type = "inline-size"
            container name = "product"
            border = "1px dashed #94a3b8"
            box:
                class = "item"
                display = flex
                gap = 8
                padding = 8
                box: width = 48 height = 48 background = "#0f766e" border radius = 8 end
                paragraph: margin = 0 content = "A wide container: side by side" end
            end
        end
    end
end

The same card, .item, sits in two containers. In the 200 pixel one it stacks, and in the 380 pixel one it is a row, with no knowledge of the window. If you narrow the window until the second container itself shrinks below 22rem, it stacks too. A container cannot be styled by its own query, only its descendants can, and an inline-size container cannot take its width from its content, so give it a width or let it fill its parent.

Hover, focus and active

Elements change look as the user acts on them. Any style property can be written with a state in front of it, and the property then applies only in that state. hover is while the pointer is over the element. focus is while the element receives the keyboard: a link, a button or an input that was reached with Tab or clicked. active is the moment it is pressed. In Persian files the words are اشاره, تمرکز and فشردن. Combine them with transition (the next lesson) for a smooth change.

web-resp-states.salam
page:
    title = "Hover, focus and active"
    global: selector = "body" font family = "system-ui, sans-serif" margin = 24 end
    button:
        padding = "10 22"
        background = "#1d4ed8"
        color = white
        border = none
        border radius = 8
        font size = 16
        cursor = pointer
        transition = "background 150ms, transform 100ms"
        hover background = "#1e40af"
        focus outline = "3px solid #f59e0b"
        focus outline offset = 2
        active transform = "scale(0.95)"
        content = "Save changes"
    end
    paragraph: content = "Move the mouse over the button, press it, or reach it with the Tab key." end
end

Hover is a convenience and never a requirement. A touch screen has no hover, and a keyboard user never hovers, so anything that appears on hover must also be reachable by focus, and a button must look clickable without a mouse over it. Use cursor = pointer on clickable things so the mouse tells the same story.

Focus deserves care, because it is how keyboard users see where they are. Never write outline = none on its own: that deletes the only marker. If you remove the default outline, replace it with something at least as visible, such as a thicker ring, a shadow or a changed border, as the next example does for the input. The pseudo-class :focus-visible is an even better trigger for a ring, since it is true when the browser thinks the focus should be shown, which is always for the keyboard and rarely for a mouse click. The states above cover only five names, so any other pseudo-class, such as :focus-visible, :disabled or :first-child, goes into a global rule with a selector.

web-resp-focus.salam
page:
    title = "Visible focus"
    global: selector = "body" font family = "system-ui, sans-serif" margin = 24 end
    global: selector = "a:focus-visible, button:focus-visible" outline = "3px solid #7c3aed" outline offset = 3 end
    box:
        display = flex
        flex direction = column
        gap = 12
        max width = 320
        label:
            content = "Your name"
            input:
                id = "name"
                display = block
                width = "100%"
                box sizing = "border-box"
                margin top = 4
                padding = 8
                border = "2px solid #94a3b8"
                border radius = 6
                outline = none
                focus border color = "#1d4ed8"
                focus box shadow = "0 0 0 4px #bfdbfe"
            end
        end
        button: padding = "8 16" content = "Send" end
        link: url = "#top" align self = "flex-start" content = "A link to tab to" end
    end
end

Press Tab in the preview to move from the input to the button and the link. Each one gets a visible marker, and the input also gets a ring from focus box shadow. An outline, unlike a border, takes no room, so showing it never moves the layout.

Generated content

CSS can create a piece of content that is not in the page: before puts it in front of an element's content and after behind it. In Salam they are state prefixes like hover, and the text is set with before content or after content. The generated piece can have its own color, size and anything else: before color = "#15803d". It is part of the look, not of the page: screen readers may or may not read it, search engines do not see it, and the user cannot select it. So use it for decoration such as check marks, arrows, required markers and quote marks, and never for the information itself.

web-resp-generated.salam
page:
    title = "Generated content"
    global: selector = "body" font family = "system-ui, sans-serif" margin = 24 line height = 1.7 end
    list:
        list style = none
        padding = 0
        item: before content = "✔  " before color = "#15803d" content = "Compiled without warnings" end
        item: before content = "✔  " before color = "#15803d" content = "Tested in the browser" end
        item: before content = "✘  " before color = "#b91c1c" content = "Not published yet" end
    end
    label:
        content = "Email"
        after content = " (required)"
        after color = "#b91c1c"
        after font size = 13
    end
    paragraph:
        content = "Read the "
        span: color = "#1d4ed8" after content = " ↗" content = "full guide" end
    end
end

The same rule is used for the first two list items, because they have the same style, and the compiler gives them one class. The value of content is a string; to use a CSS keyword such as open-quote or none write before generated content = open-quote. A generated piece shows only when it has a content, even an empty one: after content = "" is the classic clearfix, an empty block after floated children that makes the parent contain them.

The CSS function attr() reads an attribute of the element into the generated text, and it is the base of a tooltip with no extra element. It cannot be written through before content, which always makes a string, so this is a job for a raw style. Salam writes any attribute starting with data as a data- attribute, so data tip = "..." becomes data-tip.

web-resp-tooltip.salam
page:
    title = "A tooltip from attr()"
    global: selector = "body" font family = "system-ui, sans-serif" margin = 60 end
    style:
        content = ".tip { position: relative; cursor: help; border-bottom: 1px dotted } .tip::after { content: attr(data-tip); position: absolute; left: 0; bottom: 130%; background: #111827; color: white; padding: 6px 10px; border-radius: 6px; white-space: nowrap; opacity: 0; pointer-events: none; transition: opacity 150ms } .tip:hover::after, .tip:focus::after { opacity: 1 }"
    end
    paragraph:
        content = "Salam compiles to "
        span: class = "tip" tab index = 0 data tip = "HyperText Markup Language" content = "HTML" end
        span: content = " and CSS." end
    end
end

The tip is hidden with opacity: 0 and pointer-events: none so that it never catches the mouse itself, and shown on :hover and on :focus. The tab index = 0 on the span makes it reachable by keyboard, so the tip is not for mouse users only.

Pointer and touch behaviour

A group of properties does not change how an element looks but how the mouse, the finger or the selection treats it. cursor picks the mouse pointer: pointer is a hand for clickable things, text an I-beam, not-allowed a forbidden sign, grab and grabbing for things you can drag, help, zoom-in and many more, and url(...) with a fallback word gives your own image. user select says whether text can be selected: none for the labels of a button or a game, all to select a whole code or id with one click, and the default auto everywhere else, so that people can copy what they read.

pointer events = none makes an element invisible to the pointer: clicks and hovers go to whatever is below. That is how the tooltip above lets the mouse through, and how you put a decoration over a button. It is not a way to disable a button, though, because the keyboard still reaches it; for that use the disabled attribute. resize shows a handle in the corner so the user can drag the box bigger: vertical, horizontal or both on text areas, and on any element that also has an overflow other than visible.

web-resp-interact.salam
import dom
import conv

mut clicks := 0

func count_click():
    clicks += 1
    dom.SetText(dom.ById("n"), "Clicks that arrived: " + conv.FormatInt(clicks))
end

component Cell(kind):
    box: padding = "8 12" background = "#e0f2fe" border radius = 6 text align = center cursor = kind content = kind end
end

page:
    title = "Cursors and pointer control"
    global: selector = "body" font family = "system-ui, sans-serif" margin = 20 end
    box:
        display = grid
        grid template columns = "repeat(auto-fit, minmax(110px, 1fr))"
        gap = 8
        Cell: kind = "pointer" end
        Cell: kind = "not-allowed" end
        Cell: kind = "grab" end
        Cell: kind = "zoom-in" end
    end
    paragraph: user select = none content = "This sentence cannot be selected with the mouse." end
    box:
        display = flex
        gap = 8
        button: id = "on" click = count_click content = "Works" end
        button: id = "off" pointer events = none opacity = 0.5 click = count_click content = "pointer events: none" end
    end
    paragraph: id = "n" content = "Clicks that arrived: 0" end
    text area: resize = vertical rows = 2 content = "Drag the corner to resize" end
end

The preview has two buttons that run the same function. The second one has pointer events = none, so a click goes through it and the count does not change. Another group is for touch screens. touch action tells the browser which finger gestures it may handle: manipulation allows scrolling and pinch-zoom but removes the double tap zoom and its small delay, and none turns every gesture over to your own script, which a drawing area or a slider needs. tap highlight color sets the color of the flash that some mobile browsers draw on a tapped link, and transparent turns it off. appearance = none removes the system look of a button, checkbox or select, so you can draw your own, and zoom scales an element and its layout box together (zoom = 1.2), unlike transform: scale, which only scales the picture.

Two new properties are not in every browser yet. interactivity = inert makes an element and everything in it unclickable, unfocusable and unselectable, like the inert attribute, and caret animation = manual stops the blinking of the text cursor so that you can animate its color yourself. Test them before you rely on them.

Scrolling

An element with overflow = auto and a fixed height becomes a scroll container, and a few properties shape how it scrolls. scroll behavior = smooth animates jumps to a position, whether from a link to an id, from scrollIntoView or from setting scrollTop, as the button below does. scrollbar width can be thin or none, scrollbar color takes the color of the thumb and then the track, and scrollbar gutter = stable keeps room for the bar even when it is not needed, so the content does not jump sideways when a long text makes it appear.

When you scroll to the end of an inner box, browsers keep going and scroll the page behind it. That is called scroll chaining, and overscroll behavior = contain stops it at the edge of the box, which suits menus, chat windows and modals. none also turns off the bounce and the pull to refresh. The versions overscroll behavior x and y are for the physical axes and block and inline follow the writing direction.

web-resp-scroll.salam
import dom

func to_top():
    dom.SetNum(dom.ById("log"), "scrollTop", 0.0)
end

page:
    title = "Scroll containers"
    global: selector = "body" font family = "system-ui, sans-serif" margin = 20 end
    box:
        id = "log"
        width = 280
        height = 120
        padding = 8
        border = "1px solid #94a3b8"
        overflow y = auto
        scroll behavior = smooth
        overscroll behavior = contain
        scrollbar width = thin
        scrollbar color = "#0f766e #e2e8f0"
        scrollbar gutter = stable
        paragraph: repeat = 12 margin = "0 0 6" content = "A line of the log" end
    end
    button: margin top = 10 click = to_top content = "Back to top" end
end

Smooth scrolling is motion. People who asked the system to reduce motion should get an instant jump, so wrap it: write the scroll-behavior: smooth rule in a @media (prefers-reduced-motion: no-preference) block. The motion lesson does the same for animations.

Paper and special modes

People do print pages: tickets, invoices, recipes. A style with media = "print" holds the rules that apply only on paper. Hide the navigation and the buttons, use black text, and decide where pages break. page break before and page break after take always to force a new sheet and avoid to keep the element with its neighbour, and page break inside = avoid keeps a table row or a card from being cut in two. page gives an element a named page type that an @page rule can size or rotate, for example one landscape sheet for a wide table.

web-resp-print.salam
page:
    title = "Printing a page"
    style: media = "print" content = ".screen-only { display: none !important } body { font-size: 11pt }" end
    global: selector = "body" font family = "system-ui, sans-serif" margin = 20 line height = 1.5 end
    box:
        class = "screen-only"
        background = "#fef3c7"
        padding = 10
        content = "This note shows on the screen and disappears from the paper."
    end
    box:
        background = "#0f766e"
        color = white
        padding = 12
        print color adjust = exact
        content = "The banner keeps its color when it is printed"
    end
    heading: size = 2 content = "Chapter one" end
    paragraph: content = "The text of the first chapter." end
    heading: size = 2 page break before = always content = "Chapter two" end
    paragraph: content = "Chapter two starts on a new sheet." end
end

Browsers normally drop background colors when printing, to save ink. print color adjust = exact asks them to keep the colors of this element, which matters for a banner or a chart. Use your browser's print preview to see the paper version of the page above, or in the browser tools emulate the print media.

Two more properties deal with devices that change your colors or sizes. In the high contrast mode of an operating system, browsers replace your colors with the user's own, and forced color adjust = none opts an element out of it. Do that only for something like a color swatch whose color is its meaning. On phones, the browser may enlarge text in a page it thinks is hard to read, and text size adjust = "100%" or none keeps the sizes you set.

Reference: responsive and interaction properties

The media element comes first, then the properties in groups. Each example is checked by the compiler, so you can paste it into a page: block. The accepted values are written in brackets after each description.

Queries and containers

media (Persian: رسانه)

Applies style properties to its parent element only while a media condition is true. It is written as a child of the element it changes, and its own properties are the ones that differ in that case.

box:
    width = 320
    background = "#e0f2fe"
    padding = 16
    content = "Narrow windows turn me red"
    media: condition = "(max-width: 600px)" background = "#fee2e2" end
end
AttributeWhat it doesExample
condition
شرط
The media query, written in parentheses as in CSS, for example (max-width: 600px), (orientation: landscape) or (prefers-color-scheme: dark). Tests can be joined with and, and a comma means or. [string]box: padding = 20 content = "Adapts to dark mode" media: condition = "(prefers-color-scheme: dark)" background = "#111827" color = "white" end end
PropertyWhat it does and what it acceptsExample
color scheme
طرح رنگ
color-scheme
Tells the browser which color schemes the page supports, so it draws form controls, scrollbars and the default background to match. normal is the default, light and dark name one scheme, light dark lets the user's system choose, and only stops the browser from overriding the page. [normal, light, dark, only; up to 3 values]box: color scheme = dark background = "#111827" color = "#f9fafb" padding = 12 content = "Dark form controls:" input: type = "text" end end
container
نگهدارنده
container
Shorthand that makes an element a query container. The value is the container name, a slash and the type: sidebar / inline-size. The name is optional. [name; up to 4 values]box: container = "panel / inline-size" border = "1px dashed #64748b" padding = 8 content = "A container named panel" end
container name
نام نگهدارنده
container-name
Gives an element a name that an @container rule can use to pick it. Without a name a query uses the nearest container. A name is a single word without quotes inside, and several names may be listed. [name / none; up to 4 values]box: container name = "sidebar" container type = "inline-size" padding = 8 content = "Queried as sidebar" end
container type
نوع نگهدارنده
container-type
Turns an element into a container that descendants can query. inline-size allows queries about its width, size about width and height (the element then needs a size of its own), and normal is the default, a box that is not a container. [normal, size, inline-size]box: container type = "inline-size" width = "50%" border = "1px dashed #64748b" content = "Descendants can ask how wide I am" end
generated content
محتوای تولیدی
content
The text, quote marks or counter that before and after insert. Written as before content a plain string is enough. As generated content a string must carry its own quote marks, and the words open-quote, close-quote and none are written bare. It has no effect without before or after. [normal / none, or strings, images, counter(), with optional quote marks]box: before generated content = open-quote after generated content = close-quote content = "Quoted text" end

Pointer, touch and appearance

PropertyWhat it does and what it acceptsExample
cursor
نشانگر
cursor
The shape of the mouse pointer over the element. Common values are pointer (a hand, for clickable things), text, not-allowed, grab, grabbing, move, help, wait, crosshair and zoom-in. url(...) uses your own image and must end with a fallback word. [image / number / auto, default, none, pointer, text, move, ...; up to 3 values; comma list]box: cursor = "not-allowed" padding = 12 background = "#f1f5f9" content = "This action is not available" end
pointer events
رویدادهای اشاره‌گر
pointer-events
Decides whether the element reacts to the mouse and touch at all. none makes it transparent to the pointer, so clicks and hovers reach whatever lies below it, and auto is the normal behaviour. Keyboard focus is not affected. [auto, none]button: pointer events = none opacity = 0.5 content = "Clicks go straight through" end
user select
انتخاب کاربر
user-select
Controls whether the user can select the text of the element. none forbids it, text allows it, all selects the whole element with one click, contain keeps a selection inside the element, and auto is the default. [auto, none, text, all, contain]box: user select = all font family = monospace padding = 8 background = "#f1f5f9" content = "sk_live_0123456789 (one click selects it all)" end
touch action
کنش لمسی
touch-action
Tells the browser which touch gestures it may handle on the element. auto allows all, manipulation allows scrolling and pinch zoom but not double tap zoom, pan-x and pan-y allow one direction, and none leaves every gesture to your script. [auto, none, manipulation, pan-x, pan-y, pan-left, ...; up to 3 values]box: touch action = "pan-y" height = 80 background = "#e0f2fe" padding = 12 content = "Swipe up and down here, sideways swipes are left to a script" end
tap highlight color
رنگ برجسته لمس
-webkit-tap-highlight-color
The color of the flash some mobile browsers draw over a link or button when it is tapped. Any color; transparent removes the flash. [color]button: tap highlight color = "transparent" padding = "10 20" content = "Tap me" end
appearance
ظاهر
appearance
Removes or restores the system look of a form control. none strips it so you can draw your own, auto is the normal look, and other values such as button, checkbox or textfield make an element look like that control. A control with none needs its own border, background and focus style. [none, auto, menulist-button, textfield, button, checkbox, ...]button: appearance = none border = "2px solid #1d4ed8" background = white color = "#1d4ed8" border radius = 20 padding = "6 18" content = "A flat button" end
resize
تغییر اندازه
resize
Shows a handle in the corner of the box so the user can drag it bigger or smaller. both, horizontal, vertical, block and inline choose the directions, and none removes the handle. It needs an overflow other than visible, which a text area already has. [none, both, horizontal, vertical, block, inline]text area: resize = vertical rows = 3 content = "Drag the corner downwards" end
zoom
بزرگ‌نمایی
zoom
Scales the element and its layout box together, so the things around it move aside. A number (1.5) or a percentage (150%). transform: scale only scales the picture and leaves the space as it was. [number / percentage / normal]box: zoom = 1.3 border = "1px solid #64748b" padding = 8 content = "30 percent bigger, and the layout follows" end
interactivity
تعامل‌پذیری
interactivity
Makes an element and everything inside it inert: not clickable, not focusable and not selectable. inert turns it on and auto is the default. A recent property that not every browser has yet. [auto / inert]box: interactivity = inert opacity = 0.6 content = "A section the user cannot interact with" button: content = "Locked" end end
caret animation
پویانمایی نشانگر
caret-animation
Controls the blinking of the text cursor in inputs. auto lets the browser blink it, and manual stops the blinking so that your own animation of caret-color is used. A recent property that not every browser has yet. [auto / manual]input: type = "text" caret animation = manual placeholder = "The cursor does not blink" end

Scrolling

PropertyWhat it does and what it acceptsExample
scroll behavior
رفتار پیمایش
scroll-behavior
Chooses how the browser moves to a new scroll position. smooth animates it when you follow a link to an id, call scrollIntoView or set scrollTop, and auto jumps at once. Keep it for people who have not asked for reduced motion. [auto, smooth]box: height = 80 overflow y = auto scroll behavior = smooth paragraph: content = "Scroll jumps in this box" end paragraph: content = "glide instead of snapping" end paragraph: content = "when a script moves them" end end
scrollbar color
رنگ نوار پیمایش
scrollbar-color
The colors of the scrollbar, the thumb first and then the track, for example #0f766e #e2e8f0. auto is the system look. [color / auto; up to 2 values]box: height = 80 overflow y = scroll scrollbar color = "#0f766e #e2e8f0" paragraph: content = "Scroll me" end paragraph: content = "A second line" end paragraph: content = "A third line" end paragraph: content = "A fourth line" end end
scrollbar gutter
حاشیه نوار پیمایش
scrollbar-gutter
Reserves room for the scrollbar so the content does not shift when it appears. stable always keeps the room, stable both-edges keeps it on both sides, and auto adds room only when the bar is shown. [auto, stable, both-edges; up to 2 values]box: height = 80 overflow y = auto scrollbar gutter = stable content = "Short text, but the room for the bar is kept" end
scrollbar width
عرض نوار پیمایش
scrollbar-width
The thickness of the scrollbar. thin is a slimmer bar, none hides it while the box still scrolls, and auto is the system default. [auto, thin, none]box: height = 60 overflow y = auto scrollbar width = thin paragraph: content = "A thin scrollbar" end paragraph: content = "on the right" end paragraph: content = "of this box" end end
overscroll behavior
رفتار فراپیمایش
overscroll-behavior
What happens when a scroll box is scrolled past its end. auto passes the scroll on to the page behind it, contain keeps it inside the box, and none also removes bounce effects and pull to refresh. Two values set the x and the y axis. [auto, contain, none; up to 2 values]box: height = 60 overflow y = auto overscroll behavior = contain paragraph: content = "The page behind" end paragraph: content = "does not move" end paragraph: content = "when this box ends" end end
overscroll behavior block
رفتار فراپیمایش بلوکی
overscroll-behavior-block
The overscroll behavior along the block axis, the direction in which lines are stacked (vertical in English and Persian). Values: auto, contain, none. [auto, contain, none]box: height = 60 overflow y = auto overscroll behavior block = contain paragraph: content = "Block axis" end paragraph: content = "is contained" end paragraph: content = "in this box" end end
overscroll behavior inline
رفتار فراپیمایش خطی
overscroll-behavior-inline
The overscroll behavior along the inline axis, the direction in which text runs (horizontal in English and Persian). Values: auto, contain, none. [auto, contain, none]box: width = 200 overflow x = auto overscroll behavior inline = none white space = nowrap content = "A long line of text that scrolls sideways and does not push the page" end
overscroll behavior x
رفتار فراپیمایش افقی
overscroll-behavior-x
The overscroll behavior on the horizontal axis whatever the writing direction. Values: auto, contain, none. [auto, contain, none]box: width = 200 overflow x = auto overscroll behavior x = contain white space = nowrap content = "A horizontal strip that keeps its swipes to itself" end
overscroll behavior y
رفتار فراپیمایش عمودی
overscroll-behavior-y
The overscroll behavior on the vertical axis whatever the writing direction. Values: auto, contain, none. [auto, contain, none]box: height = 60 overflow y = auto overscroll behavior y = none paragraph: content = "No bounce" end paragraph: content = "and no pull to refresh" end paragraph: content = "from this box" end end

Print and display modes

PropertyWhat it does and what it acceptsExample
page
برگه
page
Assigns the element to a named page type. An @page rule with that name, written in a style element, can then set the size or orientation of the sheets this element is printed on. auto is the default. [an ident / auto]box: page = "wide" overflow x = auto content = "A wide table can print on a landscape sheet named wide" end
page break after
شکستن صفحه پس از
page-break-after
Says whether a page break may come after the element when printing. always forces a new sheet, avoid tries to keep the element with what follows, left and right force a break to a left or right page, and auto leaves it to the browser. [auto, always, avoid, left, right]heading: size = 2 page break after = avoid content = "A heading that stays with its text" end
page break before
شکستن صفحه پیش از
page-break-before
Says whether a page break may come before the element when printing. always starts the element on a new sheet, avoid keeps it with what comes before, left and right start it on a left or right page, and auto leaves it to the browser. [auto, always, avoid, left, right]heading: size = 2 page break before = always content = "Chapter two starts on a new sheet" end
page break inside
شکستن صفحه درون
page-break-inside
Says whether a page break may fall inside the element when printing. avoid keeps a card, a table row or a figure in one piece, and auto allows a break anywhere. [auto, avoid]box: page break inside = avoid border = "1px solid #94a3b8" padding = 12 content = "A card that is never cut in two on paper" end
print color adjust
تنظیم رنگ کاغذی
print-color-adjust
Chooses whether browsers keep the background colors and images of an element when printing. economy is the default and lets the browser drop them to save ink, and exact keeps them as written. [economy, exact]box: print color adjust = exact background = "#0f766e" color = white padding = 12 content = "This banner keeps its color on paper" end
forced color adjust
تنظیم رنگ اجباری
forced-color-adjust
Controls whether the browser may replace your colors in a forced colors mode such as Windows high contrast. auto allows it, none keeps your colors for this element, and preserve-parent-color keeps the text color of the parent. Opt out only where the color itself is the meaning. [auto, none, preserve-parent-color]box: forced color adjust = none background = "#16a34a" color = white padding = 8 content = "Status: online" end
text size adjust
تنظیم اندازه متن
text-size-adjust
Controls the automatic text enlargement that some mobile browsers apply to pages they think are hard to read. A percentage scales the adjustment and none (or 100%) keeps the sizes you wrote. auto is the default. [a percentage / none]paragraph: text size adjust = "100%" font size = 16 content = "Mobile browsers will not enlarge this text on their own" end

Try it yourself

  • Make a page with a header, a sidebar and a main part in a grid. At 700 pixels and below, use a media element to stack them in one column, and check it by dragging the window narrower.
  • Give a button a hover and a focus style, then put outline = none on it and press Tab. Can you still see where you are? Put a visible replacement back.
  • Add a dark mode to one of your pages with custom properties and prefers-color-scheme, then switch your system between light and dark to see it change.
  • Write a print rule that hides every navigation link and starts each h2 on a new sheet, and look at it in the print preview.