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".
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
endThe 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.
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
endThe 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.
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
endThe 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) { ... }.
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
endThe 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.
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
endHover 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.
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
endPress 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.
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
endThe 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.
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
endThe 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.
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
endThe 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.
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
endSmooth 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.
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
endBrowsers 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
| Attribute | What it does | Example |
|---|---|---|
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 |
| Property | What it does and what it accepts | Example |
|---|---|---|
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
| Property | What it does and what it accepts | Example |
|---|---|---|
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
| Property | What it does and what it accepts | Example |
|---|---|---|
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
| Property | What it does and what it accepts | Example |
|---|---|---|
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
mediaelement to stack them in one column, and check it by dragging the window narrower. - Give a button a
hoverand afocusstyle, then putoutline = noneon 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
h2on a new sheet, and look at it in the print preview.