Page structure and lists
A page that only uses boxes looks fine in a browser and is a maze for everyone who cannot see it. This lesson shows the elements that name the parts of a page, the elements for lists, and the ones for things that open and close, and it ends with a complete blog page built from them.
Boxes and landmarks
box and span are the generic containers. A box is a block, it starts on a new line and takes the full width. A span sits inside a line of text. Neither one says anything about its content, so they are the right tools when the only reason for the container is styling.
When a part of the page has a job, there is an element that names it. header holds the introduction, nav a group of navigation links, main the content the page is about, aside something related but separate, and footer the closing part. section is a themed group of content that usually starts with a heading, article is a piece that makes sense alone, such as a post or a comment, and search wraps the controls for searching. Browsers draw all of them like a plain box. The difference is for the people who cannot see the page: a screen reader lists these parts as landmarks and lets its user jump to the main content or to the navigation in one key press. Search engines read the outline in the same way.
page:
title = "Landmarks"
global: selector = "body" font family = "system-ui, sans-serif" margin = 0 line height = 1.5 end
header: background = "#0f766e" color = "white" padding = "10px 20px"
h1: content = "Garden Notes" margin = 0 font size = "1.3rem" end
end
nav: background = "#ccfbf1" padding = "8px 20px" display = flex gap = 16
link: url = "#soil" content = "Soil" end
link: url = "#water" content = "Water" end
end
box: display = flex gap = 20 padding = 20
main: flex = 1
section: id = "soil"
heading: size = 2 content = "Soil" end
paragraph: content = "Dig in compost before you plant." end
end
section: id = "water"
heading: size = 2 content = "Water" end
paragraph: content = "Water early, when the air is cool." end
end
end
aside: width = 160 background = "#f1f5f9" padding = 12 border radius = 8
strong: content = "Tip" end
paragraph: content = "A thick layer of mulch keeps the soil damp." end
end
end
footer: padding = "10px 20px" border top = "1px solid #cbd5e1" color = "#475569"
small: content = "Garden Notes, written by hand." end
end
endThe example puts a header and a nav above, then a main with two sections and an aside next to it, then a footer. The id on each section is what the links #soil and #water point to: a link that starts with # scrolls to the element with that id. The display = flex and gap lines are CSS and are explained in the CSS lessons.
A page can have only one main, and it cannot sit inside an article. The compiler reports an error for a second one (E120). It also refuses a header or a footer inside another header or footer, and an address inside an address. A header or footer inside an article or a section is fine: it then belongs to that part and not to the whole page. When a page has two nav elements, tell them apart with aria label, for example aria label = "Main", so that a screen reader can announce which one it is on.
Lists
list is a bullet list. ordered list is a numbered one, for steps and rankings where the order matters. Only item elements can sit directly inside a list, and an item holds the text of the entry in its content. To nest a list, put it inside an item, after the content, as the example does under Olives. Browsers use a different marker for each level.
page:
title = "Lists"
global: selector = "body" font family = "system-ui, sans-serif" margin = "1.5rem" line height = 1.6 end
heading: size = 2 content = "Shopping" end
list:
item: content = "Bread" end
item:
content = "Olives"
list:
item: content = "Green" end
item: content = "Black" end
end
end
item: content = "Tea" end
end
heading: size = 2 content = "Countdown" end
ordered list: reversed = true
item: content = "Mix the dough" end
item: content = "Let it rise" end
item: content = "Bake" end
end
heading: size = 2 content = "Chapters" end
ordered list: start = 4 type = "I"
item: content = "The road" end
item: content = "The inn" end
item: value = 9 content = "The return" end
end
endAn ordered list has three attributes. reversed counts down, so a list of three items is numbered 3, 2, 1. start is the number of the first item, and it can be zero or negative. type chooses the kind of marker: 1, a, A, i or I. On a single item, value sets that item's number and the items after it carry on from there, which is why the last chapter above is IX. A bullet list ignores all of these. The marker can also be changed with the CSS property list style type, for example persian for Persian digits.
menu is a list of commands, for example the buttons of a toolbar. It holds item elements like list does. Do not use it for the links of a site: that is a nav with a list inside.
Description lists
A description list is for name and value pairs: a glossary, the details of a product, the facts in a profile. A term is the name, and the description after it is the value. One term can have several descriptions, as the Footer entry in the first list shows, and several terms can share one description. Wrap each group in a box when you want to style a group as a unit, or to keep the groups visibly apart, as in the second list.
page:
title = "Definitions"
global: selector = "body" font family = "system-ui, sans-serif" margin = "1.5rem" line height = 1.6 end
description list:
term: content = "Header" font weight = "bold" end
description: content = "The top of a page or of a section." end
term: content = "Footer" font weight = "bold" end
description: content = "Closing notes, links and the author." end
description: content = "It can also close a section." end
end
description list:
box: margin top = 16
term: content = "Name" font weight = "bold" end
description: content = "Salam" end
end
box: margin top = 16
term: content = "Kind" font weight = "bold" end
description: content = "Programming language" end
end
end
endYou always write term and description. The compiler tells the two situations apart by the parent: directly inside the list they are the plain elements, and inside a box that sits directly inside a list they are the variants called term in box and description in box. They produce the same tags. Outside a description list neither is allowed, and the compiler says so.
Details and summary
details is a block the reader can open and close, with no script at all. Its first child must be a summary, and the compiler refuses it anywhere else. The summary is the line that is always visible and acts as the button. Everything after the summary is hidden until the block is opened. With open = true a block starts open. Give several blocks the same name and they become an exclusive group: opening one closes the others, which is exactly an accordion. A block without a name stays independent.
page:
title = "Questions"
global: selector = "body" font family = "system-ui, sans-serif" margin = "1.5rem" max width = "30rem" end
global: selector = "details" border = "1px solid #cbd5e1" border radius = 8 padding = "8px 12px" margin bottom = 8 end
global: selector = "summary" cursor = pointer font weight = "bold" end
details: name = "faq" open = true
summary: content = "What is a page block?" end
paragraph: content = "It holds everything on the page." end
end
details: name = "faq"
summary: content = "Can two be open at once?" end
paragraph: content = "Not here. They share the name faq, so opening one closes the other." end
end
details:
summary: content = "And this one?" end
paragraph: content = "It has no name, so it opens and closes on its own." end
end
endIn the example the first two blocks share the name faq, and the third has no name. Click them to see the difference. The global lines at the top only style the boxes and the pointer. They use plain tag names as selectors.
Dialog
dialog is a box that floats above the page, for a message or a question. It has two ways of appearing. With open = true it is shown at once, with no dimming of the page behind it. To show it as a modal, a dialog that dims the page, traps the keyboard focus and closes on the Esc key, you have to ask the browser from a script. In Salam that is one call from a click handler: dom.Call0(dom.ById("saved"), "showModal"), and "close" in the same way to close it. The attribute closedby says what else may close it: closerequest (the Esc key and the device's close gesture), any (Esc or a click outside) or none (only your own code).
import dom
page:
title = "Dialog"
global: selector = "body" font family = "system-ui, sans-serif" margin = "1.5rem" end
button: content = "Save the draft" click = (): dom.Call0(dom.ById("saved"), "showModal") end end
paragraph: id = "state" content = "Nothing saved yet." end
dialog: id = "saved" closedby = "any" padding = 20 border radius = 10
heading: size = 2 content = "Saved" margin top = 0 end
paragraph: content = "Your draft is stored. Press Esc, click outside or use the button." end
button:
content = "Close"
click = (): dom.SetText(dom.ById("state"), "Draft saved.") dom.Call0(dom.ById("saved"), "close") end
end
end
endThe page starts with import dom, which is needed for the calls. dom.ById finds the element by its id, and the compiler checks that an element with that id exists in the same page. dom.SetText changes the text of the paragraph. Events and the dom package get their own lessons later.
Figures and addresses
figure wraps something that belongs with the text but can stand on its own: a picture, a chart, a listing. The figure caption inside it names the figure, and a screen reader connects the two. The image gets its own text description in the alt attribute, and the next lesson explains why that is a different thing from the caption. address is for contact details of the author of the nearest article or of the whole page, not for any postal address you happen to mention. Browsers set it in italic, and the example turns that off with font style = normal.
page:
title = "Figure"
global: selector = "body" font family = "system-ui, sans-serif" margin = "1.5rem" max width = "30rem" end
figure: margin = 0
image: url = "data:image/svg+xml,%3Csvg%20xmlns=%27http://www.w3.org/2000/svg%27%20viewBox=%270%200%20260%20120%27%3E%3Crect%20width=%27260%27%20height=%27120%27%20fill=%27%23bae6fd%27/%3E%3Ccircle%20cx=%27200%27%20cy=%2740%27%20r=%2724%27%20fill=%27%23facc15%27/%3E%3Crect%20y=%2790%27%20width=%27260%27%20height=%2730%27%20fill=%27%2316a34a%27/%3E%3C/svg%3E" alt = "A yellow sun over a green field" width = 260 height = 120 end
figure caption: content = "Figure 1. The field at noon." color = "#475569" font size = "0.9rem" end
end
address: margin top = 20 font style = normal
content = "Garden Notes"
break: end
link: url = "mailto:hello@example.com" content = "hello@example.com" end
end
endA whole page
Here is a blog post that uses most of the elements above. The page header holds the name of the site and a search with its form. A nav follows. The main holds one article, which has a header of its own with the title and the date in a time element, and two section parts that each start with a level 2 heading. An aside next to it lists related reading, and the page footer holds the address. Read the code from the outside in, and notice that every element has a reason to be there.
page:
title = "A blog post"
global: selector = "body" font family = "system-ui, sans-serif" margin = 0 line height = 1.6 color = "#1e293b" end
header: display = flex justify content = "space-between" align items = center padding = "10px 20px" background = "#0f766e" color = "white"
strong: content = "Garden Notes" end
search:
form: input: type = "search" placeholder = "Search" aria label = "Search the blog" end end
end
end
nav: display = flex gap = 16 padding = "8px 20px" background = "#ccfbf1" aria label = "Main"
link: url = "#" content = "Home" end
link: url = "#" content = "Archive" end
end
box: display = flex gap = 24 padding = 20
main: flex = 1
article:
header:
heading: size = 1 content = "Why mulch matters" margin bottom = 4 end
paragraph: margin top = 0 color = "#64748b"
content = "By Sara, "
time: datetime = "2026-10-26" content = "26 October 2026" end
end
end
section:
heading: size = 2 content = "Less watering" end
paragraph: content = "A layer of straw keeps the sun off the soil." end
end
section:
heading: size = 2 content = "Fewer weeds" end
paragraph: content = "Seeds in the dark do not sprout." end
end
end
end
aside: width = 170 aria label = "Related"
heading: size = 2 content = "Related" font size = "1.1rem" end
list: padding inline start = 20
item: link: url = "#" content = "Compost basics" end end
item: link: url = "#" content = "Rain barrels" end end
end
end
end
footer: padding = "10px 20px" border top = "1px solid #cbd5e1"
address: font style = normal content = "Garden Notes, 12 Orchard Lane" end
end
endReference: structure and list elements
Every element of this lesson is below, with its attributes and a short example. The examples are checked by the compiler, so you can paste any of them inside a page: block. The attributes that every element has, such as id and title, are in the lesson on the head and global attributes.
Containers and landmarks
box (Persian: جعبه; HTML: <div>)
A generic block container (HTML div). It starts on a new line and takes the full width. It has no meaning of its own, so use it only to group things for layout or style when no other element fits.
box: padding = 12 background = "#f1f5f9" border radius = 8
paragraph: content = "Two paragraphs share one box." end
paragraph: content = "The box gives them a background." end
end
section (Persian: بخش; HTML: <section>)
A themed group of content, almost always with a heading at its start. A page can have many sections; give each a heading so the outline makes sense.
section:
heading: size = 2 content = "Watering" end
paragraph: content = "Water early in the morning." end
end
article (Persian: مقاله; HTML: <article>)
A self-contained piece that would make sense if it were copied somewhere else: a blog post, a news story, a product card, a comment. Articles can be nested, for example comments inside a post.
article:
heading: size = 2 content = "Mulch matters" end
paragraph: content = "A layer of straw keeps the soil damp." end
end
aside (Persian: کناری; HTML: <aside>)
Content that is related to the main content but could be removed without breaking it: a sidebar, a note, a pull quote. Screen readers announce it as a complementary landmark.
aside: padding = 12 background = "#fef9c3" border radius = 8
strong: content = "Tip" end
paragraph: content = "Water the roots, not the leaves." end
end
header (Persian: سرصفحه; HTML: <header>)
The introduction of the page or of a section: a title, a logo, a menu. Directly in the page it is the banner landmark. Inside an article or section it introduces only that part. It cannot be placed inside another header or footer.
header: background = "#0f766e" color = "white" padding = 12
h1: content = "Garden Notes" margin = 0 end
end
footer (Persian: پاورقی; HTML: <footer>)
The closing part of the page or of a section: author, copyright, related links. Directly in the page it is the content information landmark. It cannot be placed inside a header or another footer.
footer: padding = 12 border top = "1px solid #cbd5e1" color = "#475569"
small: content = "Written by hand in Salam." end
end
nav (Persian: ناوبری; HTML: <nav>)
A group of links for moving around the site or the page. Not every group of links is navigation, only the main ones. When a page has several, tell them apart with aria label.
nav: aria label = "Main" display = flex gap = 16
link: url = "#" content = "Home" end
link: url = "#" content = "Archive" end
end
main (Persian: اصلی; HTML: <main>)
The content the page exists for. A page can have only one, and it cannot be inside an article, aside, header, footer or nav. A screen reader can jump straight to it.
main: max width = 600
heading: size = 1 content = "Why mulch matters" end
paragraph: content = "The page content goes here." end
end
search (Persian: جستجو; HTML: <search>)
Wraps the controls used to search the page or the site. It is a landmark of its own, so a screen reader can jump to the search box. Put a form inside it.
search:
form: input: type = "search" placeholder = "Search" aria label = "Search the blog" end end
end
address (Persian: آدرس; HTML: <address>)
Contact information for the author of the nearest article or of the whole page. It is not for every postal address in a text. Browsers show it in italic; font style = normal turns that off. It cannot contain another address.
address: font style = normal
content = "Garden Notes"
break: end
link: url = "mailto:hello@example.com" content = "hello@example.com" end
end
Lists
list (Persian: فهرست; HTML: <ul>)
A bullet list (HTML ul). Use it when the order of the entries does not matter. Only item elements can be its direct children.
list:
item: content = "Bread" end
item: content = "Olives" end
item: content = "Tea" end
end
ordered list (Persian: فهرست شمارهدار; HTML: <ol>)
A numbered list (HTML ol) for steps and rankings, where the order matters. Its attributes change the counting and the look of the numbers.
ordered list:
item: content = "Mix the dough" end
item: content = "Let it rise" end
item: content = "Bake" end
end
| Attribute | What it does | Example |
|---|---|---|
reversedمعکوس | A boolean. With true the list counts down, so three items are numbered 3, 2, 1. The items stay in the order you wrote them. [bool] | ordered list: reversed = true
item: content = "Third place" end
item: content = "Second place" end
item: content = "Winner" end
end |
startشروع | A whole number, the number of the first item. It may be zero or negative. The default is 1 (or the last number when the list is reversed). [int] | ordered list: start = 5
item: content = "Fifth" end
item: content = "Sixth" end
end |
typeنوع | The kind of marker: 1 for numbers (the default), a or A for letters, i or I for Roman numerals in lower or upper case. [enum: ol_type] | ordered list: type = "A"
item: content = "Apples" end
item: content = "Pears" end
end |
item (Persian: مورد; HTML: <li>)
One entry of a list, an ordered list or a menu (HTML li). Its text goes in content; a nested list goes after it.
list:
item:
content = "Olives"
list:
item: content = "Green" end
item: content = "Black" end
end
end
end
| Attribute | What it does | Example |
|---|---|---|
valueمقدار | A whole number. In an ordered list it sets the number of this item, and the items after it continue from there. In a bullet list it has no effect. [int] | ordered list:
item: content = "One" end
item: value = 10 content = "Ten" end
item: content = "Eleven" end
end |
menu (Persian: منو; HTML: <menu>)
A list of commands, such as the buttons of a toolbar (HTML menu). It holds item elements like a list. Site navigation is a nav with a list, not a menu.
menu:
item: button: content = "Cut" end end
item: button: content = "Copy" end end
item: button: content = "Paste" end end
end
Description lists
description list (Persian: فهرست تعریف; HTML: <dl>)
A list of name and value groups (HTML dl): a glossary, the facts about a product. Its children are term and description elements, or box elements that each wrap one group.
description list:
term: content = "Salam" end
description: content = "A programming language." end
term: content = "Page" end
description: content = "What the browser shows." end
end
term (Persian: واژه; HTML: <dt>)
The name part of a group (HTML dt). It may be followed by one or more description elements. It is allowed directly inside a description list, and inside a box there.
description list:
term: content = "Colour" end
description: content = "Teal" end
end
description (Persian: شرح; HTML: <dd>)
The value part of a group (HTML dd). A term can have several descriptions. It is allowed directly inside a description list, and inside a box there.
description list:
term: content = "Sizes" end
description: content = "Small" end
description: content = "Large" end
end
term in box (Persian: واژه; HTML: <dt>)
The same tag as term, used when the name sits inside a box that wraps one group of a description list. You write term; the compiler picks this variant from the parent. It is refused anywhere else.
description list:
box:
term: content = "Kind" end
description: content = "Language" end
end
end
description in box (Persian: شرح; HTML: <dd>)
The same tag as description, used inside a box that wraps one group of a description list. You write description; the compiler picks this variant from the parent.
description list:
box:
term: content = "Born" end
description: content = "2018" end
description: content = "Tehran" end
end
end
Details, dialog and figures
details (Persian: جزئیات; HTML: <details>)
A block the reader can open and close without any script. Its first child must be a summary, and the rest is hidden until the block is open.
details:
summary: content = "More about mulch" end
paragraph: content = "Straw, bark or leaves all work." end
end
| Attribute | What it does | Example |
|---|---|---|
nameنام | A string that makes details blocks with the same name an exclusive group: opening one closes the others. Blocks without a name do not affect each other. [string] | details: name = "tips"
summary: content = "Spring" end
paragraph: content = "Sow seeds." end
end
details: name = "tips"
summary: content = "Autumn" end
paragraph: content = "Plant bulbs." end
end |
openباز | A boolean. With true the block is open when the page loads. The reader can still close it. [bool] | details: open = true
summary: content = "Read this first" end
paragraph: content = "It starts open." end
end |
summary (Persian: خلاصه; HTML: <summary>)
The always visible line of a details block; it works as the button that opens and closes it. It must be the first child of details.
details:
summary: content = "Shipping" font weight = "bold" end
paragraph: content = "Orders leave on Monday." end
end
dialog (Persian: پنجره گفتگو; HTML: <dialog>)
A box that floats above the page, for a message or a question. With open = true it is shown at once. To show it as a modal call showModal on it from a click handler, and close to hide it.
dialog: open = true padding = 16 border radius = 8
paragraph: content = "Shown without a script." end
end
| Attribute | What it does | Example |
|---|---|---|
openباز | A boolean. With true the dialog is visible when the page loads, as a normal, non-modal box with no dimmed backdrop. [bool] | dialog: open = true
paragraph: content = "I am open." end
end |
closedbyبستن با | What may close a modal dialog besides your own code: closerequest (the Esc key and the device's close gesture), any (a close request or a click outside the dialog) or none (only code). [enum: closed_by] | dialog: closedby = "any"
paragraph: content = "Click outside to close." end
end |
figure (Persian: شکل; HTML: <figure>)
Content that goes with the text but can stand on its own: an image, a chart, a code listing. It may hold one figure caption.
figure:
preformatted: content = "1 + 1 = 2" end
figure caption: content = "Listing 1. A sum." end
end
figure caption (Persian: زیرنویس شکل; HTML: <figcaption>)
The caption of a figure (HTML figcaption). A figure can have only one.
figure:
paragraph: content = "A chart would be here." end
figure caption: content = "Figure 2. Rain per month." font size = "0.9rem" end
end
Try it yourself
- Write the landmarks of your own home page: a
headerwith a title, anavwith three links, amainwith two sections and afooter. Then add a secondmainand read the error. - Make a recipe: an
ordered listof steps and, in adescription list, the ingredients with their amounts. Make the steps start from 0. - Build a page with a question and answer for each of three topics using
details. Give them onename, then remove the name from one of them and see what changes. - Add a button to the blog page that opens a
dialogasking the reader to subscribe, and let a click outside close it.