Head, scripts and global attributes
A web page is more than what you see. It has a title, a language, a direction, descriptions for search engines, scripts, and a set of attributes that any element can carry. This lesson covers all of that: the head of the page, scripts and templates, the global attributes, ARIA for assistive technology, data attributes, and how to split a site into reusable parts.
- Settings of the page itself
- Meta tags, links and base
- Styles, scripts and noscript
- Templates, slots and shadow trees
- Attributes that every element has
- Popovers without a script
- ARIA: when the element is not enough
- Data attributes
- Splitting a site into parts
- Reference: head elements and global attributes
- Try it yourself
Settings of the page itself
The first lines inside page:, before any element, describe the page and not something on it. title is the text on the browser tab, in bookmarks and in a search result. lang says which language the text is in, and the values are en, fa and ar. Screen readers choose their voice from it and browsers choose the right quotation marks and word breaks. dir sets the direction of the text: ltr or rtl, or the words left to right and right to left. favicon is the address of the small icon on the tab.
A file written in English starts with lang = "en" and dir = "ltr", and a Persian file with fa and rtl, so you write these two only when the page differs from its file. Any style property written at this level, such as background, color or font family, styles the whole page body.
page:
title = "Corner Bakery"
lang = "en"
dir = "ltr"
favicon = "/favicon.svg"
background = "#fdf6ec"
color = "#3b2a1a"
font family = "Georgia, serif"
padding = 28
meta: name = "description" content = "Fresh bread every morning." end
heading: size = 1 content = "Corner Bakery" end
paragraph: content = "Fresh bread every morning." end
paragraph: content = "The tab says Corner Bakery, the page language is English and the text runs left to right." end
endThe page does not show most of what you set here, so it is worth knowing what the compiler builds for you. Every page starts with <!doctype html>, then a head that holds <meta charset="utf-8">, which makes every letter of every language display correctly, and <meta name="viewport" content="width=device-width, initial-scale=1">, which tells phones to use the real screen width instead of pretending to be a wide desktop. You never have to write these two. Without a title, the tab says "Salam Page".
Meta tags, links and base
Some elements describe the page and are never drawn. You can write them anywhere inside page:, and the compiler moves them into the head: meta, head link, base and style.
meta carries a fact as a pair, name and content. A description is the summary under a search result. robots with noindex asks search engines to skip the page. theme-color colours the browser bar on a phone. For link previews in chat apps, the Open Graph protocol uses property instead of name: og:title, og:description, og:image. And http_equiv lets a tag behave like an HTTP header.
head link connects the page to another address and names the relation in rel. stylesheet loads CSS, icon sets an icon, canonical says which address is the real one when the same page can be reached in several ways, preload asks the browser to start downloading a font or image early, and alternate with a hreflang points to the translation. Its address is url. The name has two words so that it does not clash with link, the clickable link in the body.
base changes the address that all relative addresses start from. It can help a page that lives deep in a site, but it changes every relative link and file after it, so most pages leave it out.
The example below puts a description and a canonical link in its head, and a button reads them back with dom.Query, which finds an element by a CSS selector, and dom.GetAttr, which reads one of its attributes.
import dom
func readHead:
html := dom.Query("html")
dom.SetText(dom.ById("out-title"), "Title: " + dom.GetText(dom.Query("title")))
dom.SetText(dom.ById("out-lang"), "Language: " + dom.GetAttr(html, "lang"))
dom.SetText(dom.ById("out-desc"), "Description: " + dom.GetAttr(dom.Query("meta[name=description]"), "content"))
dom.SetText(dom.ById("out-canon"), "Canonical: " + dom.GetAttr(dom.Query("link[rel=canonical]"), "href"))
end
page:
title = "Reading the head"
meta: name = "description" content = "A page that reads its own head." end
head link: rel = "canonical" url = "https://example.com/head" end
global: selector = "body" font family = "system-ui, sans-serif" margin = "1.5rem" end
button: click = readHead content = "Read the head" end
paragraph: id = "out-title" content = "Title: ?" end
paragraph: id = "out-lang" content = "Language: ?" end
paragraph: id = "out-desc" content = "Description: ?" end
paragraph: id = "out-canon" content = "Canonical: ?" end
endThe compiler already writes the charset and the viewport tags. Writing a second charset only gives the page two of them. Add a meta to say something the compiler cannot know: the description, the author, the preview of the page.
Styles, scripts and noscript
Salam has style properties and event attributes for most work, but two elements are kept for raw text. style holds plain CSS in its content, for the things the properties do not cover: @keyframes, custom properties, rules for printing or for dark mode. With media the rules apply only when the query matches. Like meta, it moves to the head.
script holds JavaScript, either in content or in a file given with url. Unlike the elements above, a script stays where you write it, so put it after the elements it changes. If the code contains double quotes, write the string between backticks. Four attributes decide when a script from a file runs. A classic script runs the moment the browser reaches it, and stops the page until it is done. defer downloads the file while the page is read and runs it after the whole page is read, in order. async runs it the moment it has arrived, in any order, which suits an independent script such as statistics. type = "module" makes it an ES module and also waits for the page like defer. nomodule is the opposite switch: browsers that know modules skip it, so it is a fallback for very old ones. For a script from another site, add integrity (a hash of the file you expect) and crossorigin, so that a changed file cannot run on your page.
In the example two scripts write into the same paragraph. The module script is written first, but the classic script comes out first, because the module waits for the page.
page:
global: selector = "body" font family = "system-ui, sans-serif" margin = "1.5rem" end
script: type = "module" content = "document.getElementById('log').textContent += ' module(waits for the page)'" end
paragraph: content = "Order of events:" end
paragraph: id = "log" content = "" end
script: content = "document.getElementById('log').textContent += ' classic(runs at once)'" end
noscript: content = "This page needs JavaScript to show the order." end
endnoscript is shown only when scripts are off or blocked. Give it a short message or a plain fallback, so that a visitor without JavaScript sees something useful instead of an empty space. In the preview above the script ran, so you cannot see it.
Templates, slots and shadow trees
A template is a block of elements that the browser reads but does not draw. Scripts copy it to build new pieces of a page. Give it shadowrootmode and it becomes something more useful: the private inside of its parent element, called a shadow tree. Styles from the rest of the page do not enter a shadow tree and its styles do not leak out, which makes it a good place for a self-contained widget.
The parent element is the host. Its own children are not drawn directly. They are shown in the slot elements of the shadow tree: a slot with a name takes the children whose slot attribute is that name, and an unnamed slot takes the rest. What you write inside a slot is shown only when nothing was given for it.
page:
global: selector = "body" font family = "system-ui, sans-serif" margin = "1.5rem" end
box:
id = "card"
template:
shadowrootmode = "open"
box:
part = "frame"
inline style = "border: 2px solid crimson; padding: 8px; border-radius: 8px"
paragraph: inline style = "margin: 0 0 6px; color: crimson" content = "Inside the shadow" end
slot: end
slot: name = "note" content = "no note" end
end
end
span: content = "Light child. " end
span: slot = "note" content = "A named note" end
end
paragraph: content = "Outside the card." end
endHere shadowrootmode = "open" lets scripts reach the tree through element.shadowRoot, and closed hides it. Three more attributes, shadowrootdelegatesfocus, shadowrootclonable and shadowrootserializable, are switches for focus, copying and saving, and are listed in the reference. A shadow tree can open a door for outside styles: give an inside element a part and the page can style it with ::part(name), and exportparts passes parts through one more level.
A style: element moves to the head, even if you write it inside a template, so it styles the outside page and not the shadow tree. Inside a shadow tree, style an element with inline style, as the example does.
Attributes that every element has
A few attributes work on any element, whatever its name. You already know content, the text of the element. These are the ones to learn first.
id names one element, uniquely in the page. Links, labels and scripts find the element by it, and the compiler checks the ids that you write in the same page, so a wrong name in dom.ById is an error at build time and not a silent bug. class is a group name. Salam makes its own classes for your style properties and shares one class between elements with identical styles, so you rarely write one yourself. When you do, remember the rule: two elements with the same class and different style properties are an error (E136), because they would fight over the class. title is a tooltip, and it does not show on touch screens, so it is only a hint. inline style takes a string of CSS and overrides everything else, which makes it the last choice. hidden hides an element. With until-found the element is hidden but the browser can still find its text in a search and open it.
The next group is about how a visitor works with the element. tabindex = 0 puts an element in the Tab order, and -1 lets only a script focus it. accesskey gives it a shortcut key. contenteditable lets the visitor edit its content. draggable lets them drag it. inert turns an element and everything in it off. autofocus moves the cursor into an element when the page opens. The example below makes a note editable, reads it on every keystroke with an input handler, and shows it in a second paragraph.
import dom
func mirror:
dom.SetText(dom.ById("status"), "The note says: " + dom.GetText(dom.ById("note")))
end
page:
global: selector = "body" font family = "system-ui, sans-serif" margin = "1.5rem" end
paragraph: content = "Click the box and type. It is a normal element that you can edit." end
box:
id = "note"
contenteditable = true
spellcheck = true
tabindex = 0
padding = 12
border = "2px dashed #64748b"
border radius = 8
content = "A short note"
input = mirror
end
paragraph: id = "status" content = "The note says: A short note" end
endFor text input there are hints for phone keyboards: inputmode picks the keyboard, enterkeyhint labels the Enter key, and autocapitalize, autocorrect, spellcheck and writingsuggestions control what the keyboard and the browser do to the text. lang and dir work on a single element too, so an English name inside a Persian paragraph can carry its own direction. translatable = "no" asks translation tools to leave a word, such as a brand name, as it is.
Do not hide important text only with title or accesskey. They help some visitors and are invisible to others. Write what matters as ordinary visible text.
Popovers without a script
A popover is a box that stays hidden until something opens it, and then floats above the page. The browser does all the work: no handler, no hidden toggling, no z-index. Give the box popover and an id. Give a button popover target with the same id. With popover = "auto" the box closes when the visitor clicks outside it or presses Escape, and it closes other auto popovers first. With manual it stays until something closes it. A second attribute, popover target action, says what the button does: toggle (the default), show or hide.
page:
global: selector = "body" font family = "system-ui, sans-serif" margin = "1.5rem" end
button: popover target = "tip" content = "What is a popover?" end
box:
id = "tip"
popover = "auto"
padding = 14
border = "1px solid #94a3b8"
border radius = 8
content = "A box that opens on top of the page. Click outside it or press Escape to close."
end
endARIA: when the element is not enough
Screen readers and other assistive tools do not see your page. They read what the browser knows about each element: its role, its name, and its state. A button is known as a button, a list as a list, and nothing more is needed. The first rule of ARIA is to use the native element when one exists. ARIA attributes exist for the cases where you build something by hand or the state changes: role names what a plain box is (progressbar, status, tablist), and the attributes written aria and a name add a label, a state or a link between elements.
Three are used all the time. aria label gives a name to an element without visible text, such as a button that holds only an icon. aria hidden hides decoration from screen readers. aria expanded and aria controls say that a button opens or closes something, and which element it is. The browser does not keep aria expanded up to date. Your code must change it each time the state changes, as the example does.
import dom
mut shown := false
func toggleMenu:
shown = not shown
button := dom.ById("menu-button")
if shown:
dom.SetAttr(button, "aria-expanded", "true")
dom.SetText(button, "Hide the menu")
else:
dom.SetAttr(button, "aria-expanded", "false")
dom.SetText(button, "Show the menu")
end
dom.SetBool(dom.ById("menu"), "hidden", not shown)
end
page:
global: selector = "body" font family = "system-ui, sans-serif" margin = "1.5rem" end
button:
id = "menu-button"
aria expanded = "false"
aria controls = "menu"
click = toggleMenu
content = "Show the menu"
end
list:
id = "menu"
hidden = true
item: content = "Home" end
item: content = "Shop" end
item: content = "Contact" end
end
endThe handler flips a variable, sets aria-expanded to the new value with dom.SetAttr, changes the button text, and shows or hides the list with dom.SetBool on its hidden property. Any other ARIA attribute is written the same way: aria, a space, then the name, such as aria live or aria describedby.
A wrong ARIA attribute is worse than none: a role = "button" on a box tells the reader that it is a button, but does not make it focusable or answer to the Enter key. When a native button can do the job, use it.
Data attributes
Sometimes an element needs to carry a small fact for your own code: the id of a record, a price, a mode. For that, write data, a space and a name. data price = "12" becomes data-price in the HTML, and several words are joined with hyphens. Values are always strings. The attribute is invisible, CSS can match it with a selector like [data-mode='dark'], and a handler can read it with dom.GetAttr.
In the example all three buttons share one handler. It receives the event, asks dom.EventTarget which button was clicked, and reads that button's data. This is a good way to avoid writing one function for each button.
import dom
func showPlan(e: i64):
plan := dom.EventTarget(e)
dom.SetText(dom.ById("chosen"), dom.GetAttr(plan, "data-plan") + " plan, " + dom.GetAttr(plan, "data-price") + " dollars a month")
end
page:
global: selector = "body" font family = "system-ui, sans-serif" margin = "1.5rem" end
box:
display = flex
gap = 8
margin = "0 0 12px"
button: data plan = "Basic" data price = "5" click = showPlan content = "Basic" end
button: data plan = "Team" data price = "12" click = showPlan content = "Team" end
button: data plan = "Company" data price = "40" click = showPlan content = "Company" end
end
paragraph: id = "chosen" content = "Choose a plan." end
endNames such as mode, lang, view, only and id are listed in the reference, but only as examples of the pattern. Any name works. Write the name in English letters if a script has to read it.
Splitting a site into parts
A page that repeats the same box ten times, or ten pages that repeat the same footer, are signs that you need parts. Salam has three tools. A component is a named, reusable element with parameters. Inside it, content: end marks the place where the children written at the call go. A const above the page holds a value that is used in several places. repeat on an element writes it that many times. The example below uses all three.
const SHOP := "Corner Bakery"
component Panel(title):
section:
padding = 14
margin = "0 0 12px"
border = "1px solid #cbd5e1"
border radius = 8
heading: size = 3 margin = "0 0 6px" content = title end
content: end
end
end
page:
title = SHOP
global: selector = "body" font family = "system-ui, sans-serif" margin = "1.5rem" end
Panel: title = "Opening hours"
paragraph: content = "Every day from 7 to 19." end
end
Panel: title = "Today's bread"
list:
item: repeat = 3 content = "A fresh loaf" end
end
end
footer: content = SHOP + ", open every day" end
endThe third tool is include, which pulls another file into this one. Put your components, or a footer shared by every page, into a file of their own, and include it at the top of each page:
component Panel(title):
section:
heading: size = 3 content = title end
content: end
end
end
This is parts.salam, which contains only the component. Then every page that needs it says:
page:
include = "parts.salam"
Panel: title = "Hours"
paragraph: content = "Every day from 7 to 19." end
end
end
include
The path is relative to the file that includes the other one. An included file may hold components and also a page: block, whose elements are placed where the include is. Including a file that does not exist is a build error, and so is a file that includes itself.
Reference: head elements and global attributes
Below are the elements that live in the head or around it, then the global attributes, ARIA and data attributes. Every example is checked by the compiler. Persian names work in any file, and the Persian lessons use them.
page (Persian: صفحه)
The page block itself. It is not an HTML tag: it stands for the whole document. Directly inside it you can set title, lang, dir and favicon, any style property (it styles the page body), and then the elements of the page. The compiler writes the <head> for you, with charset and viewport already in it.
page:
title = "Corner Bakery"
lang = "en"
background = "#fdf6ec"
paragraph: content = "Fresh bread every morning." end
end
| Attribute | What it does | Example |
|---|---|---|
faviconنماد | The small icon the browser shows on the tab. It takes the address of an image, such as an .svg, .png or .ico file. It becomes a <link rel="icon"> in the head. Addresses that could run code, such as javascript:, are refused. [url] | page:
favicon = "/favicon.svg"
paragraph: content = "The tab has an icon." end
end |
meta (Persian: متا; HTML: <meta>)
Information about the page that is not shown on it: a description for search engines, the colour of the browser bar, the text that a chat app shows when someone shares your link. It goes in the head by itself, wherever you write it. A meta has no children.
meta: name = "description" content = "Fresh bread every morning." end
| Attribute | What it does | Example |
|---|---|---|
nameنام | The kind of information this tag carries. Common names are description (the summary under a search result), viewport, theme-color, robots (tell search engines not to index with noindex) and author. Always paired with content. [string] | meta: name = "robots" content = "noindex" end |
contentمحتوا | The value of the information named by name, property or http_equiv. It is plain text. For description, write one or two sentences of about 150 characters. [string] | meta: name = "author" content = "Ali Rezaei" end |
propertyخاصیت | Used by the Open Graph protocol, which chat apps and social sites read to make a preview card of your link. The names start with og:, such as og:title, og:description and og:image. Use property instead of name for them. [string] | meta: property = "og:title" content = "Corner Bakery" end |
charsetنویسهگان | Declares the text encoding of the file. The compiler already puts <meta charset="utf-8"> first in every head, and utf-8 is the only value you want, so you almost never write this yourself. [charset] | meta: charset = "utf-8" end |
http_equivمعادل اچتیتیپی | Makes the tag act like an HTTP header. refresh reloads or redirects after some seconds, content-security-policy limits what the page may load, and x-ua-compatible is an old setting. Prefer real server headers when you can. [string] | meta: http_equiv = "content-security-policy" content = "default-src 'self'" end |
mediaرسانه | A media query that says when the tag applies. It is mostly used with theme-color, so the browser bar can have one colour for light mode and another for dark mode. [media_query] | meta: name = "theme-color" content = "#0f766e" media = "(prefers-color-scheme: light)" end |
head link (Persian: پیوند سر; HTML: <link>)
Connects the page to another file or address and says what the relation is. Style sheets, icons, fonts to load early, the canonical address of the page and the feed of a blog are all written this way. It is called head link to keep it apart from link, the clickable anchor that goes in the body. rel and url are required.
head link: rel = "stylesheet" url = "/site.css" end
| Attribute | What it does | Example |
|---|---|---|
relرابطه | The kind of relation, as one word or several separated by spaces. The useful ones are stylesheet, icon, canonical (the one true address of this page), preload and preconnect (start work early), alternate, manifest and mask-icon. [token (list)] | head link: rel = "canonical" url = "https://example.com/bakery" end |
typeنوع | The MIME type of the linked file, so the browser can skip what it cannot use. Examples: text/css, image/svg+xml, font/woff2. [mime] | head link: rel = "icon" type = "image/svg+xml" url = "/icon.svg" end |
crossoriginمیانمبدا | How a file from another site is requested. anonymous sends no cookies, use-credentials sends them. Fonts must always be loaded with anonymous, even from your own site, or a preload is wasted. [enum: crossorigin] | head link: rel = "preload" as = "font" type = "font/woff2" url = "/fonts/a.woff2" crossorigin = "anonymous" end |
sizesاندازهها | For rel = "icon": the pixel sizes of the icon, such as 32x32 or 180x180, or any for a scalable SVG. Several sizes are separated by spaces. [string] | head link: rel = "icon" sizes = "32x32" url = "/icon-32.png" end |
asبه عنوان | For rel = "preload": what kind of file is being loaded, so the browser can set the right priority. Values: style, script, font, image, fetch. [token] | head link: rel = "preload" as = "style" url = "/site.css" end |
mediaرسانه | A media query. The linked file is used only when it matches, which is how a print style sheet or a large-screen style sheet is linked. [media_query] | head link: rel = "stylesheet" url = "/print.css" media = "print" end |
referrer policyسیاست ارجاع | How much of the current address is sent along when the file is requested. Values: no-referrer, origin, same-origin, strict-origin-when-cross-origin (the usual default) and unsafe-url. [enum: referrer_policy] | head link: rel = "stylesheet" url = "https://cdn.example.com/a.css" referrer policy = "no-referrer" end |
disabledغیرفعال | true keeps a style sheet linked but switched off. A script can switch it on later. Style sheets are the only thing that honours it. [bool] | head link: rel = "stylesheet" url = "/dark.css" disabled = true end |
hreflangزبان مقصد | The language of the linked page. With rel = "alternate" it points search engines to the translated version of this page. [lang] | head link: rel = "alternate" hreflang = "fa" url = "/fa/" end |
fetch priorityاولویت دریافت | A hint about how important this download is: high, low or auto. Use high sparingly, for the one thing the first screen cannot do without. [enum: fetch_priority] | head link: rel = "preload" as = "image" url = "/hero.jpg" fetch priority = "high" end |
integrityیکپارچگی | A hash of the expected file, written as sha384- and the base64 digest. The browser refuses the file if it does not match, so a changed file on someone else's server cannot run on your page. For files from another origin, add crossorigin too. [string] | head link: rel = "stylesheet" url = "https://cdn.example.com/a.css" integrity = "sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC" crossorigin = "anonymous" end |
imagesrcsetمجموعه منبع تصویر | For preloading a responsive image: the same list of files and widths that the image itself has, such as a-480.jpg 480w, a-960.jpg 960w. Used with as = "image" and imagesizes. [srcset] | head link: rel = "preload" as = "image" url = "/a-480.jpg" imagesrcset = "/a-480.jpg 480w, /a-960.jpg 960w" imagesizes = "100vw" end |
imagesizesاندازههای تصویر | How wide the preloaded image will be on the page, as a sizes value such as 100vw or (max-width: 600px) 100vw, 600px. It lets the browser pick the right file from imagesrcset. [string] | head link: rel = "preload" as = "image" url = "/a-480.jpg" imagesrcset = "/a-480.jpg 480w, /a-960.jpg 960w" imagesizes = "(max-width: 600px) 100vw, 600px" end |
blockingمسدودکننده | render tells the browser not to draw the page until this file has loaded. Use it only for a style sheet that must not flash. [enum: blocking (list)] | head link: rel = "stylesheet" url = "/theme.css" blocking = "render" end |
colorرنگ | A colour for rel = "mask-icon", the single-colour Safari pinned-tab icon. [color] | head link: rel = "mask-icon" url = "/mask.svg" color = "#0f766e" end |
urlنشانی | The address of the linked file. It is written as href in the HTML. Relative addresses (/site.css, css/site.css) and full ones both work. [url] | head link: rel = "stylesheet" url = "css/site.css" end |
base (Persian: پایه نشانی; HTML: <base>)
Sets the address that every relative link and file on the page is measured from, and the default target of links. There can be only one, and it changes every relative address after it, so use it with care.
base: url = "https://example.com/docs/" end
| Attribute | What it does | Example |
|---|---|---|
urlنشانی | The base address, written as href in the HTML. A link to intro.html then goes to https://example.com/docs/intro.html. [url] | base: url = "/docs/" end |
targetهدف | Where links open when they do not say otherwise: _self (the same tab, the default), _blank (a new tab, also new), _parent or _top. [enum: target] | base: target = "new" end |
style (Persian: ظاهر سفارشی; HTML: <style>)
A block of raw CSS, for the rules that the style properties cannot express: @keyframes, custom properties (--brand), @media print and prefers-color-scheme. Its text is written as it is, so use the content attribute. Like meta, it moves to the head by itself.
style: content = "@keyframes pulse { to { opacity: 0.4 } }" end
| Attribute | What it does | Example |
|---|---|---|
mediaرسانه | A media query for the whole block. The rules apply only when it matches, for example print or (prefers-color-scheme: dark). [media_query] | style: media = "(prefers-color-scheme: dark)" content = "body { background: #111; color: #eee }" end |
blockingمسدودکننده | render holds the first drawing of the page until the style rules are parsed. It rarely matters for inline CSS. [enum: blocking (list)] | style: blocking = "render" content = "body { margin: 0 }" end |
script (Persian: اسکریپت سفارشی; HTML: <script>)
A piece of JavaScript, either written in content or loaded from a file with url. It stays where you write it, so put it after the elements it changes. Most Salam pages do not need it, because event attributes and the dom package cover the usual work, but it is the way to use a JavaScript library.
script: content = "document.title = 'Hello'" end
| Attribute | What it does | Example |
|---|---|---|
crossoriginمیانمبدا | How the file is requested when it comes from another origin: anonymous or use-credentials. It is needed with integrity, and for readable error messages from other origins. [enum: crossorigin] | script: url = "https://cdn.example.com/lib.js" crossorigin = "anonymous" end |
referrer policyسیاست ارجاع | How much of your address is sent when the script is fetched. Same values as on head link, such as no-referrer or origin. [enum: referrer_policy] | script: url = "https://cdn.example.com/lib.js" referrer policy = "origin" end |
fetch priorityاولویت دریافت | A hint about how important this script is: high, low or auto. [enum: fetch_priority] | script: url = "/analytics.js" fetch priority = "low" async = true end |
integrityیکپارچگی | The expected hash of the script file, as sha384- and a base64 digest. If someone changes the file, the browser refuses to run it. Always use it for scripts from a CDN. [string] | script: url = "https://cdn.example.com/lib.js" integrity = "sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC" crossorigin = "anonymous" end |
blockingمسدودکننده | render keeps the page from being drawn until this script has run. Only for a script that must run before the first paint. [enum: blocking (list)] | script: blocking = "render" content = "document.documentElement.dataset.theme = 'dark'" end |
typeنوع | module makes the script an ES module: it can use import, it is in strict mode, and it waits until the page has been read, like defer. Without it the script is a classic script that runs the moment the browser meets it. Other values, like application/json, make the browser keep the text as data without running it. [string] | script: type = "module" content = "console.log('module')" end |
nomoduleبدون ماژول | true makes browsers that understand modules skip this script. Pair it with a module version to give an old fallback to old browsers. [bool] | script: nomodule = true url = "/legacy.js" end |
asyncناهمگام | true downloads the file while the page is read and runs it as soon as it arrives, in any order. Good for independent scripts such as statistics. It applies only to scripts with a url. [bool] | script: url = "/stats.js" async = true end |
deferبا تاخیر اجرا | true downloads the file while the page is read and runs it after the whole page has been read, in the order written. Good for scripts that need the elements to exist. It applies only to scripts with a url. [bool] | script: url = "/app.js" defer = true end |
urlنشانی | The address of the script file, written as src in the HTML. A script has either a url or its own content, not both. [url] | script: url = "/js/app.js" defer = true end |
noscript (Persian: بدون اسکریپت; HTML: <noscript>)
Content that is shown only when scripts are switched off or blocked. Use it for a short message or a plain fallback, so a visitor without JavaScript is not left with a blank area.
noscript: content = "Turn on JavaScript to use the calculator." end
template (Persian: قالب; HTML: <template>)
A block that the browser reads but does not show. Scripts clone it to make new pieces of the page. With the shadowrootmode attribute it turns into a shadow root of its parent, which is a private tree of elements with its own styles. See the shadow DOM section above.
box:
template:
shadowrootmode = "open"
paragraph: inline style = "color: teal" content = "I live in a shadow root" end
end
end
| Attribute | What it does | Example |
|---|---|---|
shadowrootmodeحالت ریشه سایه | Turns the template into a shadow root of its parent element. open lets scripts reach the root through element.shadowRoot, closed hides it. Without this attribute a template is only inert storage. [enum: shadow_root_mode] | box:
template: shadowrootmode = "open" paragraph: content = "Inside" end end
end |
shadowrootdelegatesfocusواگذاری تمرکز ریشه سایه | true makes a click on the non-focusable parts of the shadow tree, and the focus() call on the host, move focus to the first focusable element inside. [bool] | box:
template: shadowrootmode = "open" shadowrootdelegatesfocus = true button: content = "Inside" end end
end |
shadowrootclonableریشه سایه کپیپذیر | true lets the shadow root be copied when the host is copied with cloneNode. Without it the copy has no shadow tree. [bool] | box:
template: shadowrootmode = "open" shadowrootclonable = true paragraph: content = "Copied too" end end
end |
shadowrootserializableریشه سایه سریالپذیر | true lets the shadow root be included when the host is turned back into HTML by getHTML(). [bool] | box:
template: shadowrootmode = "open" shadowrootserializable = true paragraph: content = "Saved with the host" end end
end |
slot (Persian: جایگاه; HTML: <slot>)
A hole inside a shadow tree where the host's own children are shown. Whatever you write inside the slot is the fallback, used when nothing is given for it.
box:
template:
shadowrootmode = "open"
slot: content = "Nothing to show" end
end
end
| Attribute | What it does | Example |
|---|---|---|
nameنام | Gives the slot a name. A child of the host with the same name in its own slot attribute lands in this slot. A slot without a name takes the children that have no slot. [string] | box:
template: shadowrootmode = "open" slot: name = "title" end end
heading: size = 2 slot = "title" content = "Hello" end
end |
Global attributes: identity, text and style
These work on every element. The include attribute is explained in the section on splitting a site.
| Attribute | What it does | Example |
|---|---|---|
idشناسه | A name for one element, unique in the page. It is what links, labels, aria controls, scripts (dom.ById) and the address fragment (page.html#intro) refer to. The compiler checks ids written in the same page, so a typo in dom.ById is a build error. [token] | paragraph: id = "intro" content = "The first paragraph." end |
classکلاس | A group name that elements can share. In Salam you rarely need it, because the compiler makes classes for your style properties itself and gives the same class to elements with identical styles. Write one when a script or an outside style sheet needs a hook. Two elements with the same class must have the same style properties, or the build stops with error E136. [string] | paragraph: class = "lead" color = "teal" content = "A class can be shared." end |
titleعنوان | Extra information about the element, shown by most browsers as a tooltip when the mouse rests on it. It is not shown on touch screens, so never put essential text in it. On the page itself, title is the text of the browser tab instead. [string] | paragraph: title = "Opens at 7, closes at 19" content = "Hover to see the hours." end |
inline_styleاستایل | A string of CSS written straight into the element, like color: crimson; font-weight: bold. It beats the classes the compiler makes. Prefer the style properties of Salam. Use this one inside a shadow tree, where outside rules do not reach, or when the CSS text comes from somewhere else. [string] | paragraph: inline style = "color: crimson; font-weight: bold" content = "Styled in place." end |
contentمحتوا | The text inside the element. It is always plain text, so < and & show as they are and never become markup. It must come before the child elements. You can build it with + from constants and numbers. [string] | paragraph: content = "Tom & Jerry, 2 < 3" end |
repeatتکرار | Writes the element several times, one after another, from one line. The number must be known when the page is built (a number or a constant). It is handy for sketches and for lists whose items are all alike. [uint] | list:
item: repeat = 3 content = "A row" end
end |
Global attributes: focus, editing and dragging
| Attribute | What it does | Example |
|---|---|---|
tabindexترتیب جهش | Where the element sits in the Tab key order. 0 adds a normally unfocusable element to the natural order, -1 lets a script focus it but keeps it out of the Tab order, and a positive number jumps the queue, which you should avoid. Buttons, links and inputs are focusable already. [int] | box: tabindex = 0 content = "Press Tab to reach me." end |
accesskeyکلید دسترسی | A key that jumps to or presses the element, used together with a modifier that depends on the browser and system (often Alt or Alt+Shift). One character, or several to try in order. Use it rarely, since it can clash with keys of the browser and of screen readers. [token (list)] | button: accesskey = "s" content = "Save (Alt+S)" end |
autofocusتمرکز خودکار | true puts the cursor in this element when the page opens. One per page. Use it on the field the visitor will almost certainly use first, such as a search box, and not on a long page where it would scroll the page away from the top. [bool] | input: type = "search" autofocus = true end |
contenteditableویرایشپذیر | Lets the visitor edit the element's content directly, like a small text editor. Values: true, false and plaintext-only (no bold or pasted formatting). Read what they typed with dom.GetText from an input handler. [enum: content_editable] | box: contenteditable = "plaintext-only" content = "Edit this text" end |
draggableکشیدنی | true lets the visitor pick the element up with the mouse and drag it; false stops images and links, which are draggable by default, from being dragged. The drag events say what happens next (see the lesson on drag events). [enum: true_false] | box: draggable = true content = "Drag me" end |
inertبیاثر | true makes the element and everything in it dead: it cannot be clicked, focused or found, and screen readers skip it. Use it for the part of a page that sits behind an open dialog. [bool] | box: inert = true content = "Nobody can click or focus anything in here" button: content = "Dead button" end end |
popoverواشو | Turns the element into a popover: hidden until something opens it, then drawn on top of everything else. auto closes it when the visitor clicks outside or presses Escape, and closes other auto popovers. manual stays until you close it. A button with popover target opens it, with no script. [enum: popover] | button: popover target = "tip" content = "Open the tip" end
box: id = "tip" popover = "auto" content = "A tip on top of the page." end |
Global attributes: language and text input
| Attribute | What it does | Example |
|---|---|---|
dirجهت | The direction of the text inside the element: ltr (left to right), or rtl (right to left). Set it on a piece of text whose direction differs from the page, such as an Arabic quote in an English page. On the page itself the same attribute sets the direction of everything. [dir] | paragraph: dir = "right to left" content = "Text that runs from the right." end |
langزبان | The language of the text inside the element, with the same values as on the page: en, fa or ar. Screen readers pick the right voice from it, browsers pick the right hyphenation and quotation marks, and a translation tool knows what to translate. [lang] | paragraph: lang = "ar" dir = "rtl" content = "مرحبا بالعالم" end |
translatableترجمهپذیر | no asks automatic translators, such as the one built into a browser, to leave the text alone. Use it for brand names, code and anything that must stay as written. yes is the default. [enum: yes_no] | span: translatable = "no" content = "Salam" end |
spellcheckغلطیاب | true asks the browser to underline misspelled words while the visitor types in the element. false turns it off, for codes, names and passwords. It matters only for editable elements. [enum: true_false] | text area: spellcheck = true content = "Teh quick brown fox" end |
inputmodeحالت ورود | Chooses which on-screen keyboard a phone shows while typing in an editable element: numeric, decimal, tel, email, url, search, text or none (no keyboard at all). It only changes the keyboard, not what is accepted. [enum: input_mode] | input: type = "text" inputmode = "numeric" end |
enterkeyhintراهنمای کلید ورود | Changes the label or icon on the Enter key of a phone's on-screen keyboard: enter, done, go, next, previous, search or send. It does not change what the key does. [enum: enter_key_hint] | input: type = "text" enterkeyhint = "search" end |
autocapitalizeحروف بزرگ خودکار | Which letters a phone keyboard capitalises by itself as the visitor types: none or off, sentences, words or characters. Use words for names and none for email addresses and codes. [enum: autocapitalize] | text area: autocapitalize = "words" content = "ali rezaei" end |
autocorrectتصحیح خودکار | on or off: lets a phone keyboard correct what the visitor types (mostly Safari). Turn it off for codes, usernames and anything that is not an ordinary word. [enum: on_off] | text area: autocorrect = "off" content = "x_user_42" end |
writingsuggestionsپیشنهاد نوشتن | false asks the browser not to offer its own writing help, such as predictive or AI suggestions, in this editable element. true allows it. [enum: true_false] | text area: writingsuggestions = "false" content = "A private note" end |
Global attributes: microdata
Microdata marks up facts about a thing, so search engines can show them in rich results.
| Attribute | What it does | Example |
|---|---|---|
itemscopeدامنه مورد | Starts a microdata item: a group of facts about one thing, such as a person, a recipe or a product. The properties inside it are marked with itemprop. Search engines read them to build rich results. [bool] | box:
item scope = true
item type = "https://schema.org/Person"
span: item prop = "name" content = "Ali Rezaei" end
end |
itemtypeنوع مورد | The kind of thing the microdata item describes, as the full address of a vocabulary type, almost always from https://schema.org/, such as https://schema.org/Recipe. It goes on the same element as itemscope. [absolute_url (list)] | box: item scope = true item type = "https://schema.org/Recipe" span: item prop = "name" content = "Barbari bread" end end |
itempropویژگی مورد | Names one fact of the microdata item, such as name, telephone or image. The text of the element is the value, and for a link, image or time the matching attribute is used. [token (list)] | box: item scope = true item type = "https://schema.org/Person" span: item prop = "telephone" content = "021-5550123" end end |
itemidشناسه مورد | A global name for the item, an address that stands for exactly this thing, so that other data can refer to it. [url] | box: item scope = true item type = "https://schema.org/Person" item id = "https://example.com/people/ali" span: item prop = "name" content = "Ali Rezaei" end end |
itemrefارجاع مورد | Lets the item use properties that sit elsewhere in the page, outside its element. It holds the ids of those elements. The compiler checks that each id exists. [idref (list)] | box: item scope = true item type = "https://schema.org/Person" item ref = "phone" end
span: id = "phone" item prop = "telephone" content = "021-5550123" end |
Global attributes: shadow trees and scripting
| Attribute | What it does | Example |
|---|---|---|
slotجایگاه | Puts this element into the named slot of its parent's shadow tree. Without it, the element goes into the unnamed slot. Not to be confused with the slot element that makes the hole. [token] | box:
template: shadowrootmode = "open" slot: name = "note" end end
span: slot = "note" content = "Shown in the note slot" end
end |
partبخش سایه | Names a piece inside a shadow tree so that style rules from the page outside can reach it with ::part(name). It is the controlled door through the shadow boundary. [token (list)] | box:
template: shadowrootmode = "open" button: part = "action" content = "Buy" end end
end
style: content = "::part(action) { color: crimson }" end |
exportpartsصادرات بخش | On a host inside another shadow tree: passes on the part names of its own shadow tree, so that the outermost page can style them too. Several names are separated by commas. [string] | box: exportparts = "action, label" content = "A host that passes its parts on" end |
isعنصر سفارشی | The name of a customised built-in element, defined from JavaScript with customElements.define("fancy-note", ..., {extends: "p"}). The element stays a normal p and gains the new behaviour. It does nothing until such a definition has been loaded. [token] | paragraph: is = "fancy-note" content = "A customised paragraph" end |
nonceنانس | A one-time secret that the server puts in both the page's content security policy and in each allowed inline script or style. It lets those pass while other inline code is blocked. It must be new on every response, so a fixed value written in the source protects nothing. [token] | script: nonce = "r4nd0m" content = "console.log('allowed')" end |
ARIA attributes
The role attribute comes first, then the ARIA attributes that Salam knows by name. All others follow the general pattern in the last row.
| Attribute | What it does | Example |
|---|---|---|
roleنقش | Tells assistive technology what the element is, when no native element says it. Examples: alert, status, dialog, tablist, progressbar. The first rule of ARIA is to use a native element when one exists: a button needs no role. [token] | box: role = "status" content = "Saved." end |
| Attribute | What it does | Example |
|---|---|---|
aria labelبرچسب دسترسیپذیری | A name for an element that has no visible text, read aloud by screen readers. Typical for a button that holds only an icon. When the element already has visible text, you do not need it. [string] | button: aria label = "Close the dialog" content = "X" end |
aria expandedباز شده دسترسیپذیری | true or false: tells whether the thing this button controls is open or closed. A script must change it every time the state changes. Browsers do not do it for you. [string] | button: aria expanded = "false" aria controls = "menu" content = "Menu" end
list: id = "menu" hidden = true item: content = "Home" end end |
aria controlsکنترلکننده دسترسیپذیری | The id of the element this one controls, such as the panel a tab button opens. It helps assistive technology jump between them. It must be the id of an element in the same page. [idref (list)] | button: aria controls = "panel" aria expanded = "true" content = "Details" end
box: id = "panel" content = "The details." end |
aria pressedفشردهشده دسترسیپذیری | For a button that switches something on and off, like a bold button in an editor: true when it is on, false when off, mixed when partly on. The label stays the same, so the screen reader says "Bold, toggle button, pressed". [string] | button: aria pressed = "true" content = "Bold" end |
aria valuenowمقدار کنونی دسترسیپذیری | The current value of a range widget made from plain elements, such as a slider or a progress bar, as a number. It is used with aria valuemin, aria valuemax and a role. The native progress, meter and range input already have it. [number] | box: role = "progressbar" aria valuenow = 40 aria valuemin = 0 aria valuemax = 100 content = "40%" end |
aria valueminکمینه مقدار دسترسیپذیری | The smallest value of the range, as a number. [number] | box: role = "slider" tabindex = 0 aria valuemin = 0 aria valuemax = 10 aria valuenow = 3 content = "3 of 10" end |
aria valuemaxبیشینه مقدار دسترسیپذیری | The largest value of the range, as a number. [number] | box: role = "progressbar" aria valuemin = 0 aria valuemax = 200 aria valuenow = 50 content = "50 of 200" end |
aria haspopupدارای واشو دسترسیپذیری | Warns that activating the element opens a popup, and says what kind: menu, listbox, tree, grid, dialog or true (a menu). [string] | button: aria haspopup = "menu" content = "Actions" end |
aria orientationجهت دسترسیپذیری | Says whether a widget such as a toolbar, slider or tab list lies horizontal or vertical, so screen readers know which arrow keys are expected. [string] | box: role = "toolbar" aria orientation = "vertical" button: content = "Cut" end button: content = "Copy" end end |
ariaدسترسیپذیری | The general pattern. Every other ARIA attribute is written the same way: the word aria, a space, then the name, such as aria live, aria describedby, aria current, aria disabled, aria required or aria labelledby. The compiler writes aria-live and so on. Values are strings. [string] | box: aria live = "polite" content = "3 new messages" end |
Data attributes
| Attribute | What it does | Example |
|---|---|---|
data modeداده حالت | A data attribute named mode, written as data-mode. Pages often use it to keep the current mode, such as light or dark, where both CSS and scripts can read it. [string] | box: data mode = "dark" content = "Dark mode on" end |
data langداده زبان | A data attribute named lang, written as data-lang. It is not the same as the real lang attribute: it only carries a value for your own scripts and styles. [string] | box: data lang = "fa" content = "Version: Persian" end |
data viewداده نما | A data attribute named view, written as data-view, for example the layout a list is shown in, grid or list. [string] | box: data view = "grid" content = "Shown as a grid" end |
data onlyداده فقط | A data attribute named only, written as data-only, often used as a filter flag, such as data-only with the value free on a button that shows only the free items. [string] | button: data only = "free" content = "Free only" end |
data idداده شناسه | A data attribute named id, written as data-id. It is the usual way to tie a row in the page to a record, such as a product number. Unlike the real id, it need not be unique. [string] | list:
item: data id = "1042" content = "Barbari bread" end
end |
dataداده | The general pattern: the word data, a space, then any name. Several words become one name with hyphens, so data user id is written data-user-id. In a handler, read it with dom.GetAttr(el, "data-user-id"). In CSS it can be matched with [data-user-id='7']. Write the name in English letters if a script has to read it. [string] | box: data user id = "7" data role = "admin" content = "Ali" end |
Try it yourself
- Add a
descriptionand an Open Graphog:titleto one of your pages, then read them back withdom.Queryanddom.GetAttras in the head example. - Change the aria example so that the button also toggles
aria pressed. What does a screen reader announce, and which of the two attributes fits a menu button better? - Make a component
Cardwith a title and acontent: endslot, use it three times withrepeatand then move it to its own file withinclude. - Put a second element with the class
cardand a different colour on the page, and read the error. Then fix it in two ways.