Forms
A form is how a page listens. This lesson covers every form element and every input type, the attributes that make the browser check what was typed, and how to read the values in your own code. Each idea has a live example, and the examples that react to the visitor really work in the preview.
The form and the submit
A form holds controls and knows where to send them. action is the address that receives the data and method is how it travels: get puts the data into the address and suits searches, post puts it into the body of the request and is the right choice for everything that changes something or is private. When the form has a file input, add enctype = "multipart/form-data". The other form attributes are in the reference below: autocomplete, novalidate, target, rel, accept charset and name.
A real send would leave the page, and the previews in this lesson must stay on it. So most examples here listen to the button and decide for themselves what happens. The click attribute of a submit button is a function that runs when the button is pressed. It receives the event, and dom.PreventDefault stops the default action, which is sending the form. That also skips the browser's checks, so the function asks for them: reportValidity checks every field and shows the browser's message at the first bad one, and as bool turns its answer into true or false. After that you read each field with dom.GetValue(dom.ById("id")) and use the text as you like. The event is the plain i64 parameter of the function.
On a page of your own you can give the same function to the submit attribute of the form. The browser then runs its checks first and calls your function only when they pass, so reportValidity is not needed.
import dom
func Join(e: i64):
dom.PreventDefault(e)
if dom.Call0(dom.ById("sign"), "reportValidity") as bool:
name := dom.GetValue(dom.ById("name"))
mail := dom.GetValue(dom.ById("mail"))
dom.SetText(dom.ById("out"), "Welcome, " + name + ". We will write to " + mail + ".")
end
end
page:
title = "Sign up"
global: selector = "body" font family = "system-ui, sans-serif" margin = "1.5rem" max width = "24rem" end
global: selector = "label" display = block margin = "0.8rem 0 0.2rem" end
global: selector = "input" padding = 6 width = "100%" box sizing = "border-box" end
form: id = "sign"
label: for = "name" content = "Your name" end
input: id = "name" name = "name" required = true placeholder = "Ada Lovelace" end
label: for = "mail" content = "Email" end
input: id = "mail" name = "mail" type = email required = true placeholder = "ada@example.com" end
button: type = submit click = Join content = "Join" margin = "1rem 0" padding = "6px 18px" end
end
paragraph: id = "out" color = "#0f766e" end
endLeave the fields empty and press Join: the browser shows its message and nothing else happens, because reportValidity answered no. Write something that is not an address in the Email field and it refuses again. Only a complete form gets past the if, which is why you can trust the values inside it, at least as far as the browser checks go.
Browser checks are for the visitor's comfort, not for security. Anyone can send a request without your page. Whatever your server receives has to be checked on the server again.
Labels
Every control needs a label. Connect them in one of two ways. Give the control an id and write the same id in for of the label, as the sign up example does: Salam checks at compile time that an element with that id exists, so a typo cannot silently break the link. Or put the control inside the label, as the pizza example further down does, and no for is needed. Either way a click on the label text focuses the control or ticks the checkbox, and a screen reader reads the label when the control gets the focus.
The placeholder attribute is not a label. It is a hint in the empty box, it vanishes when the visitor types and many people find the grey text hard to read. Use it for an example of the format, and keep the label.
Input types
input is one element with many faces. Its type decides the face, the keyboard a phone shows, and what the browser checks. Without a type it is a text box. The table lists every type that Salam names, with its Persian name, which you use in Persian files.
| Type | Persian | What the visitor gets | What the browser checks |
|---|---|---|---|
text | متن | A one line box. | Only minlength, maxlength and pattern. |
password | گذرواژه | A box that hides the characters. | The same as text. Set autocomplete to current-password or new-password. |
email | ایمیل | A box and a phone keyboard with the @ key. | The value looks like name@host. With multiple it is a comma separated list. |
number | عدد | A box with up and down arrows and a number keyboard. | It is a number, within min and max, on the step grid. |
tel | تلفن | A box and the phone keypad. | Nothing, because numbers differ so much. Add a pattern. |
url | نشانی | A box and a keyboard with slash and dot. | A full address with a scheme, such as https://. |
search | جستجو | A box, often with a clear button and a search key on the keyboard. | Nothing. |
checkbox | تیک | A square that is on or off. | With required it has to be ticked. Sent only when ticked. |
radio | رادیویی | A round dot. Controls with the same name form a group of which one is chosen. | With required one of the group must be chosen. |
submit | ارسال | A button that sends the form. value is its label. | Runs the checks of all the other fields. |
button | دکمه | A button that does nothing until you give it a click handler. | Nothing. |
reset | بازنشانی | A button that puts every field back to its starting value. | Nothing. |
date | تاریخ | A calendar picker. The value is written 2026-10-26. | A real date, within min and max. |
time | زمان | A time picker. The value is written 14:30. | A real time, within min and max. |
color | رنگ ورودی | A colour picker. The value is written #ff8800. | Nothing. It always has a colour. |
range | بازه | A slider between min and max. | Nothing. It always has a value. |
file | پرونده | A button that opens the file chooser. accept and multiple shape it. | With required a file must be chosen. |
hidden | پنهان | Nothing. It sends a value the visitor does not see. | Nothing. Do not trust the value: the visitor can change it. |
textarea | ناحیه متن | A multi line box, the same as the text area element. | The same as text area. |
jalali date | تاریخ شمسی | A box that writes Persian digits and slashes by itself. | A real Jalali date. See the section on it below. |
Other HTML types, such as datetime-local, month, week and image, are not in Salam's list but you can write them as quoted strings, for example type = "month". The image type is a submit button drawn as a picture, and it uses url for the picture, alt for its text and width and height.
page:
title = "Input types"
global: selector = "body" font family = "system-ui, sans-serif" margin = "1.5rem" end
form:
display = grid
grid template columns = "7rem 12rem"
gap = "0.6rem 1rem"
label: for = "n" content = "Number" end
input: id = "n" type = number end
label: for = "p" content = "Phone" end
input: id = "p" type = tel end
label: for = "u" content = "Web page" end
input: id = "u" type = url end
label: for = "q" content = "Search" end
input: id = "q" type = search end
label: for = "d" content = "Date" end
input: id = "d" type = date end
label: for = "t" content = "Time" end
input: id = "t" type = time end
label: for = "c" content = "Colour" end
input: id = "c" type = color end
label: for = "v" content = "Volume" end
input: id = "v" type = range end
end
endThe form above is a grid with two columns, set with display = grid and grid template columns, so that every label stands to the left of its control. gap is the space between rows and columns. Open it on a phone and each control brings its own keyboard.
Letting the browser check
Most of the checking comes from attributes on the control. required forbids an empty value. minlength and maxlength bound the number of characters, and pattern takes a regular expression that the whole value must match. For numbers and dates, min, max and step set the range and the grid of allowed values. size only changes how wide a text box looks, and does not check anything. readonly keeps a value from being edited but still sends it, while disabled also keeps it from being sent. autofocus puts the cursor in a field when the page opens, multiple allows several files or addresses, and autocomplete tells the browser what the field holds so that it can offer the saved value.
Besides the message that the browser shows, your code can ask each control about its validity. The example reads it on every keystroke with the input event. dom.GetProp(field, "validity") gives an object whose properties are true or false: valueMissing when a required field is empty, tooShort when it is shorter than minlength, patternMismatch when it does not match the pattern, and rangeUnderflow and rangeOverflow when a number is outside min and max. dom.EventTarget(e) tells the shared handler which field it was called for.
import dom
func Check(e: i64):
field := dom.EventTarget(e)
v := dom.GetProp(field, "validity")
mut text := "Looks good."
if dom.GetBool(v, "valueMissing"): text = "This field is required."
else dom.GetBool(v, "tooShort"): text = "Use at least 3 characters."
else dom.GetBool(v, "patternMismatch"): text = "Letters only, no spaces."
else dom.GetBool(v, "rangeUnderflow"): text = "You must be 18 or older."
else dom.GetBool(v, "rangeOverflow"): text = "Please enter an age up to 99."
end
dom.SetText(dom.ById(dom.GetStr(field, "id") + "-note"), text)
end
page:
title = "Validation"
global: selector = "body" font family = "system-ui, sans-serif" margin = "1.5rem" end
style: content = "input:user-invalid { border: 2px solid #dc2626 } input:user-valid { border: 2px solid #16a34a }" end
form:
label: for = "user" content = "Username" end
input: id = "user" required = true minlength = 3 pattern = "[A-Za-z]+" input = Check end
paragraph: id = "user-note" content = "At least 3 letters." end
label: for = "age" content = "Age" end
input: id = "age" type = number required = true min = 18 max = 99 input = Check end
paragraph: id = "age-note" content = "From 18 to 99." end
end
endThe two border colours come from a CSS rule written with the page wide style element: input:user-invalid and input:user-valid. These match only after the visitor has touched the field and left it, so a form that is still empty does not show red from the first second. The older :invalid matches at once and looks angry on a fresh form.
One handler can serve many fields. dom.EventTarget(e) is the control that fired the event, and dom.GetStr(field, "id") is its id, so the message for the field named user goes into the element with the id user-note.
Longer text
For text that may take several lines use text area. It has rows and cols for its starting size, maxlength and minlength for the length, and wrap: with soft long lines only wrap on the screen, with hard the line breaks are also put into the text that is sent, which needs cols. The text between the tags in HTML is the starting text, and in Salam it is content. An input with type = "textarea" is the same element written differently.
A counter that tells the visitor how many characters are left is a small kindness when you set a limit. The example reads textLength from the box, which counts characters, not bytes, so it is right for Persian text as well.
import dom
const LIMIT := 80
func Count:
used := dom.GetNum(dom.ById("bio"), "textLength") as i64
dom.SetText(dom.ById("left"), "" + (LIMIT - used) + " characters left")
end
page:
title = "Long text"
global: selector = "body" font family = "system-ui, sans-serif" margin = "1.5rem" end
label: for = "bio" content = "About you" end
text area:
id = "bio"
name = "bio"
rows = 4
cols = 40
maxlength = LIMIT
placeholder = "A few words about yourself"
input = Count
display = block
margin = "0.4rem 0"
end
small: id = "left" content = "80 characters left" end
endCheckboxes, radio buttons and groups
A checkbox is a yes or no. Several checkboxes with the same name are sent as a list of the ticked ones. A radio is one choice out of a set: all radio buttons that share a name belong together and the browser keeps one of them ticked. value is the text that is sent when the control is chosen, and checked chooses it from the start. To read a choice in code, use dom.GetBool(control, "checked").
Wrap a related set in a field set with a legend. A sighted visitor sees a box with a title. A screen reader announces the legend as the question when the visitor enters the group, and then reads each option, which a bare row of radio buttons cannot do. The form in the example reacts to the change event, which fires on the form for every change of any control inside it.
import dom
func Show:
mut size := "small"
if dom.GetBool(dom.ById("s-large"), "checked"): size = "large" end
mut extras := ""
if dom.GetBool(dom.ById("x-cheese"), "checked"): extras = extras + " cheese" end
if dom.GetBool(dom.ById("x-olives"), "checked"): extras = extras + " olives" end
dom.SetText(dom.ById("out"), "Size: " + size + ". Extras:" + extras)
end
page:
title = "Pizza order"
global: selector = "body" font family = "system-ui, sans-serif" margin = "1.5rem" end
global: selector = "label" display = block margin = "0.3rem 0" end
form: change = Show
field set:
legend: content = "Size" end
label: input: id = "s-small" type = radio name = "size" value = "small" checked = true end span: content = " Small" end end
label: input: id = "s-large" type = radio name = "size" value = "large" end span: content = " Large" end end
end
field set:
legend: content = "Extras" end
label: input: id = "x-cheese" type = checkbox name = "extra" value = "cheese" end span: content = " Cheese" end end
label: input: id = "x-olives" type = checkbox name = "extra" value = "olives" end span: content = " Olives" end end
end
end
paragraph: id = "out" content = "Size: small. Extras:" end
endA field set can also be switched off in one go: disabled on the set disables every control inside. The next example reads a checkbox and sets disabled with dom.SetBool.
import dom
func Toggle:
same := dom.GetBool(dom.ById("same"), "checked")
dom.SetBool(dom.ById("ship"), "disabled", same)
end
page:
title = "Grouped fields"
global: selector = "body" font family = "system-ui, sans-serif" margin = "1.5rem" max width = "26rem" end
global: selector = "fieldset input" display = block margin = "0.4rem 0" padding = 5 end
form:
field set:
legend: content = "Billing address" end
input: name = "street" placeholder = "Street" end
input: name = "town" placeholder = "Town" end
end
label: input: id = "same" type = checkbox change = Toggle end span: content = " Ship to the same address" end end
field set: id = "ship"
legend: content = "Shipping address" end
input: name = "street2" placeholder = "Street" end
input: name = "town2" placeholder = "Town" end
end
end
endChoosing from a list
select is a drop-down list, and option is each line of it. The visitor sees the text of the option, and the form sends its value, so give a short code that your server understands. selected picks the starting option and disabled greys one out: a first line that is disabled and selected makes a good prompt, such as Choose a city. option group puts a heading over several options and needs a label. multiple turns the list into a box where several lines can be chosen, and size says how many lines are visible.
import dom
func Pick:
dom.SetText(dom.ById("out"), "You chose: " + dom.GetValue(dom.ById("city")))
end
page:
title = "Drop-down list"
global: selector = "body" font family = "system-ui, sans-serif" margin = "1.5rem" end
label: for = "city" content = "Where do you live? " end
select: id = "city" name = "city" change = Pick
option: value = "none" content = "Choose a city" disabled = true selected = true end
option group: label = "Europe"
option: value = "oslo" content = "Oslo" end
option: value = "rome" content = "Rome" end
end
option group: label = "Asia"
option: value = "tokyo" content = "Tokyo" end
option: value = "seoul" content = "Seoul" disabled = true end
end
end
paragraph: id = "out" content = "You chose: none" end
endWhen the visitor should also be allowed to type something that is not in your list, use a data list. It is a list of suggestions that has no look of its own. Give it an id and name it in the list attribute of a text input: the browser then offers the matching suggestions while the visitor types.
import dom
func Echo:
dom.SetText(dom.ById("out"), "Searching for " + dom.GetValue(dom.ById("lang")))
end
page:
title = "Suggestions"
global: selector = "body" font family = "system-ui, sans-serif" margin = "1.5rem" end
label: for = "lang" content = "Favourite language " end
input: id = "lang" list = "langs" placeholder = "Start typing" input = Echo end
data list: id = "langs"
option: value = "Salam" end
option: value = "Python" end
option: value = "Rust" end
option: value = "Scheme" end
end
paragraph: id = "out" content = "Searching for" end
endButtons
A button has a type. A submit button sends the form, a reset button puts all fields back, and a button button does nothing until a script gives it something to do. Inside a form a button without a type is a submit button, which is how many forms get sent by accident: always write the type.
A submit button with a name and a value tells you which one was pressed, so one form can have Save and Delete. The buttons can also override the form for one press: form action, form method, form enctype, form target and form novalidate replace the form's own attribute. A Save draft button with form novalidate sends a half filled form. With form = "id" a button may stand outside the form it belongs to. In a submit handler on a real page, dom.GetProp(e, "submitter") is the button that was used. In the example each button has its own click handler instead, which reads the button's value with dom.GetStr(dom.EventTarget(e), "value"). The reset button below the form belongs to it through form = "note".
import dom
func Pressed(e: i64):
dom.PreventDefault(e)
dom.SetText(dom.ById("out"), "Pressed: " + dom.GetStr(dom.EventTarget(e), "value"))
end
page:
title = "Buttons"
global: selector = "body" font family = "system-ui, sans-serif" margin = "1.5rem" end
global: selector = "button" padding = "6px 12px" margin = "0.4rem 0.4rem 0 0" end
form: id = "note"
input: id = "t" name = "title" placeholder = "Title" end
button: type = submit name = "do" value = "save" click = Pressed content = "Save" end
button: type = submit name = "do" value = "draft" click = Pressed content = "Save draft" end
button: type = reset content = "Clear" end
button: type = button value = "help" click = Pressed content = "Help" end
end
button: type = reset form = "note" content = "Clear from outside" end
paragraph: id = "out" content = "Nothing pressed yet." end
endTwo more groups of attributes belong to buttons. popover target and popover target action show or hide an element that has the popover attribute, with no code. command and commandfor give a command such as show-modal to a dialog. Both are newer, so check the browsers your visitors use.
Showing results
Three elements show values instead of asking for them. output holds a result, like the number beside a slider; its for lists the controls it is calculated from, and Salam checks the ids. progress is a bar for how much of a job is done, with value out of max. meter shows a value on a known scale, and low, high and optimum tell the browser which part of the scale is good: the bar turns green, yellow or red.
import dom
func Move:
level := dom.GetValue(dom.ById("level"))
dom.SetText(dom.ById("shown"), level + " %")
dom.SetStr(dom.ById("bar"), "value", level)
dom.SetStr(dom.ById("gauge"), "value", level)
end
page:
title = "Output, progress, meter"
global: selector = "body" font family = "system-ui, sans-serif" margin = "1.5rem" end
global: selector = "progress, meter" display = block width = "16rem" margin = "0.6rem 0" end
form:
label: for = "level" content = "Battery level " end
input: id = "level" type = range min = 0 max = 100 value = 40 input = Move end
output: id = "shown" for = "level" content = "40 %" end
end
progress: id = "bar" value = 40 max = 100 end
meter: id = "gauge" value = 40 min = 0 max = 100 low = 25 high = 75 optimum = 100 end
endDrag the slider and all three change. progress and meter look alike and mean different things. A task that is going to finish is a progress. A reading that goes up and down, such as battery charge, is a meter.
Files
A file input opens the file chooser. accept filters what it offers, with MIME types such as image/png, wildcards such as image/* and extensions such as .pdf. It is a convenience: a visitor can still pick another file, so check the type on the server. multiple allows several files. A form that sends files must be a post form with enctype = "multipart/form-data". In code, dom.GetProp(input, "files") is the list of chosen files, dom.CountOf counts them and dom.ItemAt picks one.
import dom
func Listed:
files := dom.GetProp(dom.ById("pics"), "files")
n := dom.CountOf(files)
mut text := "No files chosen."
if n > 0: text = "Chosen: " + dom.GetStr(dom.ItemAt(files, 0), "name") end
if n > 1: text = text + " and " + (n - 1) + " more" end
dom.SetText(dom.ById("out"), text)
end
page:
title = "File upload"
global: selector = "body" font family = "system-ui, sans-serif" margin = "1.5rem" end
form: method = post enctype = "multipart/form-data"
label: for = "pics" content = "Pictures " end
input: id = "pics" name = "pics" type = file accept = "image/png, image/jpeg, .webp" multiple = true change = Listed end
end
paragraph: id = "out" content = "No files chosen." end
endThe Jalali date
Salam has an input type of its own for Persian dates, jalali date (in Persian files تاریخ شمسی). It is a text box that types Persian digits whatever keyboard the visitor has, adds the slashes, and tells the browser the date is invalid when the month or the day does not exist, which also respects leap years. Latin and Arabic digits that the visitor pastes are converted. The box shows the digits ۱۴۰۵/۰۸/۰۴, and the same date with Latin digits, 1405/08/04, is kept in the data-value attribute for your code to read with dom.GetAttr.
import dom
func Changed:
day := dom.GetAttr(dom.ById("birth"), "data-value")
dom.SetText(dom.ById("out"), "Stored value: " + day)
end
page:
title = "Persian date"
global: selector = "body" font family = "system-ui, sans-serif" margin = "1.5rem" end
label: for = "birth" content = "Birthday (Jalali) " end
input: id = "birth" name = "birth" type = "jalali date" placeholder = "1380/01/01" change = Changed end
paragraph: id = "out" content = "Stored value:" end
endThe value that a real form send puts on the wire is the text as the box shows it, with Persian digits. Convert on the server, or read data-value in a submit handler.
Reference: every form element
Each element is shown with an example, and the attributes that belong only to that element are listed under it. The attributes that every element has are in the lesson on the head and global attributes, and the events, such as submit, input and change, are in the lesson on keys and form events. The examples are checked by the compiler, so you can paste any of them into a page: block. Persian names work in any file, and the Persian lessons use them.
form (Persian: فرم; HTML: <form>)
A form collects what the visitor types or picks and sends it to an address. Inputs, buttons and the other form controls go inside it. A form can also run code instead of sending: write a submit handler that calls dom.PreventDefault and reads the fields yourself, as the examples in this lesson do. Forms cannot be nested.
form: action = "/search" method = get
label: for = "q" content = "Search" end
input: id = "q" name = "q" type = search end
button: type = submit content = "Go" end
end
| Attribute | What it does | Example |
|---|---|---|
nameنام | A name for the form, so that scripts and the browser can find it among the forms of the page. It is not sent to the server. [string] | form: name = "login"
input: name = "user" placeholder = "User" end
end |
targetهدف | Where the answer of the server is shown: same (the current window, the default), new (a new window or tab), parent or top for pages inside frames. The HTML names _self, _blank, _parent and _top work too. [enum: target] | form: action = "/report" target = new
button: type = submit content = "Open the report" end
end |
relرابطه | Says how the target page relates to this one, as a list of words separated by spaces. With target = new write noopener, so that the new page cannot reach back into yours. [token (list)] | form: action = "/pay" target = new rel = "noopener noreferrer"
button: type = submit content = "Pay" end
end |
autocompleteتکمیل خودکار | on lets the browser fill the fields from what it remembers, off asks it not to. The default is on. Each field can override the form with its own autocomplete. [string] | form: autocomplete = "off"
input: name = "code" placeholder = "One use code" end
end |
actionمقصد ارسال | The address that receives the data when the form is sent. Without it the form is sent to the address of the page itself. [url] | form: action = "/subscribe" method = post
input: type = email name = "mail" end
button: type = submit content = "Subscribe" end
end |
methodروش | How the data travels. get puts it in the address, which suits searches because the result can be bookmarked. post puts it in the body of the request, which is right for anything that changes data or is private. dialog sends nothing and just closes the dialog that holds the form. [enum: form_method] | form: action = "/login" method = post
input: type = password name = "secret" end
button: type = submit content = "Log in" end
end |
enctypeرمزگذاری | How a post form packs its data. The default application/x-www-form-urlencoded is right for text. Choose multipart/form-data when the form has a file input, and text/plain only for debugging. [enum: form_enctype] | form: method = post enctype = "multipart/form-data"
input: type = file name = "photo" end
button: type = submit content = "Upload" end
end |
novalidateبدون اعتبارسنجی | A boolean. When it is true the browser does not check required, pattern and the other rules when the form is sent. Use it when you validate in your own code. [bool] | form: novalidate = true
input: name = "mail" type = email required = true end
button: type = submit content = "Send anyway" end
end |
accept charsetنویسهگان پذیرفته | The text encodings the server accepts, separated by spaces. The default is the encoding of the page, and since pages are UTF-8 you rarely need it. [charset] | form: accept charset = "UTF-8"
input: name = "word" placeholder = "Any word" end
end |
input (Persian: ورودی; HTML: <input>)
A single form control. Its type decides what it is: a text box, a checkbox, a colour picker, a button and so on, 20 kinds in all that Salam names (see the table in the lesson). It has no content and no children. Give it a name, because a control without a name is not sent, and a label, because a control without a label is a puzzle for a screen reader.
label: for = "city" content = "City" end
input: id = "city" name = "city" placeholder = "Oslo" end
| Attribute | What it does | Example |
|---|---|---|
widthعرض | The width in pixels. It only has an effect on type = "image". For every other kind use the CSS width. [ulength] | input: type = "image" url = "go.png" alt = "Go" width = 80 end |
heightارتفاع | The height in pixels. Like width it only matters for type = "image". [ulength] | input: type = "image" url = "go.png" alt = "Go" height = 40 end |
typeنوع | The kind of control. The names Salam knows are text (the default), password, email, number, tel, url, search, checkbox, radio, submit, button, reset, date, time, color, range, file, hidden, textarea and jalali date. Other HTML types, such as datetime-local, month, week and image, can be written as quoted strings. [enum: input_type] | input: type = range min = 0 max = 10 value = 5 end |
nameنام | The key under which the value is sent. Several radio buttons with the same name form one group, and the browser lets only one of them be checked. A control without a name is not sent at all. [string] | input: name = "email" type = email end |
valueمقدار | The starting value of the field, and what is sent. For a checkbox or a radio button it is the text that is sent when the box is checked. For buttons it is the label. [string] | input: name = "nick" value = "guest" end |
placeholderجانگهدار | A short hint shown inside an empty field and gone when the visitor starts typing. It is not a label: it disappears, it is often low in contrast, and not all screen readers read it. Use a label for the name and the placeholder for an example. [string] | input: name = "phone" type = tel placeholder = "0912 345 6789" end |
requiredالزامی | A boolean. The form cannot be sent while the field is empty (or, for a checkbox, unchecked), and the browser says so. It also sets the :required state for CSS. [bool] | input: name = "name" required = true placeholder = "Required" end |
checkedتیک خورده | A boolean for checkboxes and radio buttons: whether the control starts checked. Reading it later in code is done with dom.GetBool(el, "checked"). [bool] | input: type = checkbox name = "news" checked = true end |
disabledغیرفعال | A boolean. A disabled control cannot be focused or changed, is drawn greyed out and is not sent with the form. If you want a field that is only unchangeable but still sent, use readonly. [bool] | input: name = "plan" value = "Free" disabled = true end |
urlنشانی | The address of the picture of an image button. It is written src in HTML. Add alt too. [url] | input: type = "image" url = "send.png" alt = "Send" end |
formفرم | The id of the form this control belongs to. It lets a control stand outside its form in the page, or be part of a form far away. [idref] | form: id = "signup" end
input: name = "mail" type = email form = "signup" end |
multipleچندتایی | A boolean for file and email: the visitor may choose several files, or write several addresses separated by commas. [bool] | input: type = file name = "docs" multiple = true end |
sizeاندازه | The visible width of a text box, counted in characters. It does not limit what can be typed (that is maxlength). For real layout use CSS width. [uint] | input: name = "zip" size = 6 placeholder = "12345" end |
autofocusتمرکز خودکار | A boolean. The control gets the keyboard focus when the page opens. Use it on one field only, and only where the visitor expects it, such as a search page. [bool] | input: type = search name = "q" autofocus = true end |
autocompleteتکمیل خودکار | Tells the browser what the field holds so it can fill it in. Use the standard words: name, email, tel, username, current-password, new-password, one-time-code, postal-code, or off. Password managers depend on these. [string] | input: type = password name = "pw" autocomplete = "new-password" end |
maxlengthبیشینه طول | A whole number. The most characters the visitor can type. The browser stops them at that point, so tell them the limit in the label or a counter. [uint] | input: name = "tag" maxlength = 12 placeholder = "12 characters at most" end |
minlengthکمینه طول | A whole number. The fewest characters that are accepted. A shorter value that the visitor typed makes the field invalid, and the form is not sent. An empty field is allowed unless it is also required. [uint] | input: name = "user" minlength = 3 placeholder = "3 characters or more" end |
readonlyفقط خواندنی | A boolean. The visitor can select and copy the text but not change it, and it is still sent with the form. Compare disabled. [bool] | input: name = "id" value = "A-1042" readonly = true end |
dirnameنام جهت | The name of an extra field that carries the text direction (ltr or rtl) of what was typed. Useful when you store text in several languages. The usual name is the field name plus .dir. [token] | input: name = "comment" dirname = "comment.dir" end |
acceptپذیرش | For file: the kinds of files the dialog offers, as a list of MIME types such as image/png, wildcards such as image/*, or extensions such as .pdf. It is a hint for the visitor, not a check, so verify files on the server. [string (list)] | input: type = file name = "doc" accept = "application/pdf, .docx" end |
form actionمقصد ارسال فرم | On a submit button or an image button: sends the form to a different address than the form's own action, only when this button is used. [url] | form: action = "/save"
input: type = submit value = "Save" end
input: type = submit value = "Save and close" form action = "/save-close" end
end |
form methodروش فرم | On a submit button: overrides the form's method for this button only, with get, post or dialog. [enum: form_method] | form: action = "/find" method = post
input: type = submit value = "Search" form method = get end
end |
form enctypeرمزگذاری فرم | On a submit button: overrides the form's enctype for this button only. [enum: form_enctype] | form: method = post
input: type = submit value = "Upload" form enctype = "multipart/form-data" end
end |
form novalidateبدون اعتبارسنجی فرم | On a submit button: skips the browser's checks when this button is used. The classic use is a Save draft button next to a Send button. [bool] | form:
input: name = "title" required = true end
input: type = submit value = "Save draft" form novalidate = true end
end |
form targetهدف فرم | On a submit button: shows the answer in a different window than the form's target says, for this button only. [enum: target] | form: action = "/print"
input: type = submit value = "Preview" form target = new end
end |
listفهرست پیشنهادها | The id of a data list whose options are offered as suggestions while the visitor types. The visitor may still type something else. Salam checks that the id exists. [idref] | input: name = "fruit" list = "fruits" end
data list: id = "fruits"
option: value = "Apple" end
option: value = "Plum" end
end |
maxبیشینه | The largest accepted value for number, range, date and time. For dates write it as text: 2026-12-31. [string] | input: type = number name = "qty" max = 10 end |
minکمینه | The smallest accepted value for number, range, date and time. [string] | input: type = number name = "age" min = 18 end |
patternالگو | A regular expression the whole value must match, for text, search, tel, url, email and password. Describe the rule in a title or next to the field, since the browser's own message is generic. [string] | input: name = "zip" pattern = "[0-9]{5}" title = "Five digits" end |
stepگام | The step between valid numbers or times. The default for number is 1, so 2.5 is refused unless you write step = "0.5" or step = "any". It is counted from min. [string] | input: type = number name = "price" step = "0.5" min = 0 end |
popover targetهدف واشو | On a button type input: the id of an element that has the popover attribute. Pressing the button shows or hides it, with no script. [idref] | input: type = button value = "Tip" popover target = "tip" end
box: id = "tip" popover = "auto" content = "Drink water." end |
alphaشفافیت رنگ | A boolean for color: the colour picker may also choose transparency. Only newer browsers support it. [bool] | input: type = color name = "tint" alpha = true end |
colorspaceفضای رنگ | For color: the colour space of the value, either limited-srgb (the default) or display-p3, which has more vivid colours on wide gamut screens. Only newer browsers support it. [enum: color_space] | input: type = color name = "tint" colorspace = "display-p3" end |
popover target actionکنش واشو | Together with popover target: what the button does to the popover. toggle (the default) shows or hides it, show only shows it and hide only hides it. [enum: popover_action] | input: type = button value = "Close tip" popover target = "tip" popover target action = hide end
box: id = "tip" popover = "auto" content = "Drink water." end |
altجایگزین | The text that stands for the picture of an image button when the picture is missing, and that screen readers read. [string] | input: type = "image" url = "send.png" alt = "Send the form" end |
contentمحتوا | Another way to write the starting text of the field: it becomes the value. Handy for a text typed straight into the page, and it is what a textarea input uses for its text. [string] | input: name = "greeting" content = "Hello" end |
textarea input (Persian: ورودی; HTML: <textarea>)
The form of text area that is written as an input with type = "textarea". It produces the same <textarea> tag, and its text comes from content. Prefer text area, which has the attributes that belong to a multi-line box (rows, cols, wrap).
input: type = "textarea" name = "note" content = "Write here" end
text area (Persian: متن بلند; HTML: <textarea>)
A box for text with several lines: a comment, a message, an address. The text inside is its starting content. The visitor can drag the corner to resize it unless CSS resize says otherwise. Line breaks the visitor types are sent as they are.
text area: name = "message" rows = 3 cols = 30 content = "Dear team," end
| Attribute | What it does | Example |
|---|---|---|
nameنام | The key under which the text is sent. [string] | text area: name = "bio" end |
formفرم | The id of the form this box belongs to, when it stands outside that form. [idref] | form: id = "post" end
text area: name = "body" form = "post" end |
requiredالزامی | A boolean. The form cannot be sent while the box is empty. [bool] | text area: name = "reason" required = true placeholder = "Why?" end |
disabledغیرفعال | A boolean. The box cannot be edited or focused and is not sent. [bool] | text area: name = "old" disabled = true content = "Archived text" end |
autofocusتمرکز خودکار | A boolean. The box gets the keyboard focus when the page opens. [bool] | text area: name = "draft" autofocus = true end |
autocompleteتکمیل خودکار | on or off (or a standard word such as street-address): whether the browser may suggest earlier text. [string] | text area: name = "address" autocomplete = "street-address" end |
rowsردیفها | A whole number: how many lines of text are visible without scrolling. For exact sizes use CSS height. [uint] | text area: name = "note" rows = 6 end |
colsستونها | A whole number: how many characters wide the box is. For exact sizes use CSS width, and width = "100%" for a box that fills its parent. [uint] | text area: name = "note" cols = 50 end |
maxlengthبیشینه طول | A whole number: the most characters the visitor can type. [uint] | text area: name = "bio" maxlength = 200 end |
minlengthکمینه طول | A whole number: the fewest characters accepted when the box is not empty. [uint] | text area: name = "review" minlength = 20 end |
placeholderجانگهدار | A hint shown while the box is empty. It does not replace a label. [string] | text area: name = "msg" placeholder = "Write your message" end |
readonlyفقط خواندنی | A boolean. The text can be selected but not changed, and it is still sent. [bool] | text area: name = "terms" readonly = true content = "Terms of use" end |
wrapشکستن خط | soft (the default) wraps long lines on the screen only. hard also puts real line breaks into the sent text at the edge of the box, and needs cols. [enum: wrap] | text area: name = "poem" cols = 30 wrap = hard end |
dirnameنام جهت | The name of an extra field that carries the text direction of what was typed, like the same attribute of input. [token] | text area: name = "note" dirname = "note.dir" end |
label (Persian: برچسب; HTML: <label>)
The caption of a form control. A click on the label focuses or toggles its control, which makes small checkboxes much easier to hit, and screen readers read it when the control is focused. Connect them with for (the id of the control) or by putting the control inside the label.
label: for = "mail" content = "Email" end
input: id = "mail" type = email end
| Attribute | What it does | Example |
|---|---|---|
forبرای | The id of the control this label names. Salam checks at compile time that an element with that id exists in the page, so a typo is an error instead of a silent loss. [idref] | label: for = "pw" content = "Password" end
input: id = "pw" type = password end |
button (Persian: دکمه; HTML: <button>)
A button with any content: text, or an image next to text. Inside a form its default type is submit, which sends the form. For a button that only runs code, say type = button or the form is sent by accident.
button: type = button content = "Click me" end
| Attribute | What it does | Example |
|---|---|---|
typeنوع | submit sends the form, reset puts every field back to its starting value and button does nothing by itself. Without a type, a button in a form is a submit button. [enum: button_type] | button: type = reset content = "Clear the form" end |
nameنام | With value: the pair that is sent when this button was the one pressed. It lets a form with several submit buttons know which one the visitor used. [string] | button: type = submit name = "action" value = "save" content = "Save" end |
valueمقدار | The value sent under the button's name when the button is pressed. In a script, dom.GetStr(button, "value") reads it. [string] | button: type = submit name = "action" value = "delete" content = "Delete" end |
disabledغیرفعال | A boolean. The button cannot be pressed and is drawn greyed out. A common use is to keep Send disabled until the form is valid. [bool] | button: type = submit disabled = true content = "Send" end |
formفرم | The id of the form this button belongs to, so the button can stand outside the form, for instance in a toolbar. [idref] | form: id = "note" end
button: type = submit form = "note" content = "Save the note" end |
autofocusتمرکز خودکار | A boolean. The button gets the keyboard focus when the page opens. [bool] | button: type = button autofocus = true content = "Start" end |
form actionمقصد ارسال فرم | On a submit button: sends the form to this address instead of the form's own action. [url] | form: action = "/save"
button: type = submit content = "Save" end
button: type = submit form action = "/publish" content = "Publish" end
end |
form methodروش فرم | On a submit button: sends the form with this method instead of the form's method. [enum: form_method] | form: action = "/item" method = post
button: type = submit content = "Save" end
button: type = submit form method = get content = "Preview" end
end |
form enctypeرمزگذاری فرم | On a submit button: packs the data in this way instead of the form's enctype. [enum: form_enctype] | form: method = post
button: type = submit form enctype = "multipart/form-data" content = "Send with files" end
end |
form novalidateبدون اعتبارسنجی فرم | On a submit button: skips the browser's checks when this button is pressed, as in a Save draft button. [bool] | form:
input: name = "title" required = true end
button: type = submit form novalidate = true content = "Save draft" end
end |
form targetهدف فرم | On a submit button: shows the answer in this window instead of the form's target. [enum: target] | form: action = "/preview"
button: type = submit form target = new content = "Preview in a new tab" end
end |
popover targetهدف واشو | The id of an element with the popover attribute that this button shows or hides, with no script. [idref] | button: popover target = "menu" content = "Menu" end
box: id = "menu" popover = "auto" content = "Home, About, Contact" end |
popover target actionکنش واشو | With popover target: toggle (the default), show or hide. [enum: popover_action] | button: popover target = "menu" popover target action = show content = "Open the menu" end
box: id = "menu" popover = "auto" content = "Home, About, Contact" end |
commandفرمان | A command the button gives to the element named by commandfor, with no script: for example show-modal and close for a dialog. A newer feature, so check browser support before relying on it. [string] | button: command = "show-modal" commandfor = "box" content = "Open" end
dialog: id = "box" content = "Hello" end |
commandforهدف فرمان | The id of the element that receives the command. Salam checks that the id exists. [idref] | button: command = "close" commandfor = "box" content = "Close" end
dialog: id = "box" open = true content = "Hello" end |
select (Persian: انتخاب; HTML: <select>)
A drop-down list: the visitor picks one of the options, or several with multiple. The value that is sent is the value of the chosen option, or its text when it has none. Use it for four or more choices; for two or three, radio buttons show the options without a click.
select: name = "fruit"
option: value = "apple" content = "Apple" end
option: value = "pear" content = "Pear" end
end
| Attribute | What it does | Example |
|---|---|---|
nameنام | The key under which the chosen value is sent. [string] | select: name = "size"
option: value = "s" content = "Small" end
end |
formفرم | The id of the form the list belongs to, when it stands outside that form. [idref] | form: id = "order" end
select: name = "size" form = "order"
option: value = "s" content = "Small" end
end |
multipleچندتایی | A boolean. The list becomes a box that shows several options at once, and the visitor can select more than one with Ctrl or Shift (or by tapping, on a phone). [bool] | select: name = "toppings" multiple = true size = 3
option: value = "cheese" content = "Cheese" end
option: value = "olives" content = "Olives" end
option: value = "corn" content = "Corn" end
end |
requiredالزامی | A boolean. The form cannot be sent until an option with a non-empty value is chosen, so make the first option a prompt with value = "". [bool] | select: name = "country" required = true
option: value = "" content = "Choose a country" end
option: value = "no" content = "Norway" end
end |
disabledغیرفعال | A boolean. The whole list is greyed out, cannot be changed and is not sent. [bool] | select: name = "plan" disabled = true
option: content = "Free" end
end |
sizeاندازه | A whole number: how many options show at once. Anything above 1 turns the drop-down into a scrolling box. [uint] | select: name = "day" size = 4
option: content = "Mon" end
option: content = "Tue" end
option: content = "Wed" end
option: content = "Thu" end
option: content = "Fri" end
end |
autofocusتمرکز خودکار | A boolean. The list gets the keyboard focus when the page opens. [bool] | select: name = "lang" autofocus = true
option: content = "English" end
end |
autocompleteتکمیل خودکار | A standard word such as country or off: lets the browser pick the option from what it knows about the visitor. [string] | select: name = "country" autocomplete = "country"
option: value = "no" content = "Norway" end
end |
option (Persian: گزینه; HTML: <option>)
One choice in a select, an option group or a data list. Its text is what the visitor sees, and its value is what is sent. In a data list the value is the suggestion.
select: name = "fruit"
option: value = "apple" content = "Apple" end
end
| Attribute | What it does | Example |
|---|---|---|
valueمقدار | The text that is sent when this option is chosen. Without it the text of the option is sent. Give a short code, not a translated sentence, so that your server does not depend on the page language. [string] | select: name = "lang"
option: value = "fa" content = "Persian" end
end |
labelبرچسب | A shorter text to show instead of the text inside the option. It is rarely needed. In a data list it is the explanation shown next to the suggestion. [string] | select: name = "unit"
option: value = "km" label = "km" content = "kilometres" end
end |
disabledغیرفعال | A boolean. The option is shown greyed out and cannot be chosen. Good for a prompt line like Choose a city, or for something sold out. [bool] | select: name = "city"
option: value = "" content = "Choose a city" disabled = true selected = true end
option: value = "oslo" content = "Oslo" end
end |
selectedانتخابشده | A boolean. The option is the chosen one when the page opens. Without it the first option is chosen. [bool] | select: name = "size"
option: value = "s" content = "Small" end
option: value = "m" content = "Medium" selected = true end
end |
option group (Persian: گروه گزینه; HTML: <optgroup>)
A named group of options inside a select. The visitor sees the name as a bold line that cannot be chosen. It must have a label, and the compiler stops without one.
select: name = "city"
option group: label = "Europe"
option: value = "oslo" content = "Oslo" end
option: value = "rome" content = "Rome" end
end
end
| Attribute | What it does | Example |
|---|---|---|
labelبرچسب | The name of the group, shown above its options. It is required. [string] | select: name = "city"
option group: label = "Asia"
option: value = "tokyo" content = "Tokyo" end
end
end |
disabledغیرفعال | A boolean. Every option of the group is greyed out and cannot be chosen. [bool] | select: name = "city"
option group: label = "Closed" disabled = true
option: value = "x" content = "Atlantis" end
end
end |
data list (Persian: فهرست پیشنهاد; HTML: <datalist>)
A list of suggestions for a text input. Link it with the input's list attribute. Unlike select it does not force a choice: the visitor may type something that is not in the list. It is invisible by itself.
input: name = "browser" list = "browsers" end
data list: id = "browsers"
option: value = "Firefox" end
option: value = "Chrome" end
end
field set (Persian: گروه فیلد; HTML: <fieldset>)
Groups related controls in a box, such as the lines of an address or a set of radio buttons. A screen reader announces the legend when the visitor enters the group, which is how a bare list of radio buttons gets its question.
field set:
legend: content = "Delivery" end
label: input: type = radio name = "d" end span: content = " Home" end end
label: input: type = radio name = "d" end span: content = " Office" end end
end
| Attribute | What it does | Example |
|---|---|---|
nameنام | A name for the group, for scripts. It is not sent. [string] | field set: name = "address"
legend: content = "Address" end
input: name = "street" end
end |
formفرم | The id of the form the group belongs to, when it stands outside that form. The group's controls are part of that form. [idref] | form: id = "f1" end
field set: form = "f1"
legend: content = "Extra" end
input: name = "extra" end
end |
disabledغیرفعال | A boolean. Every control inside is disabled at once, apart from those in the legend. Switch it in a script with dom.SetBool(el, "disabled", true), as the lesson example does. [bool] | field set: disabled = true
legend: content = "Locked" end
input: name = "locked" value = "Cannot change" end
end |
legend (Persian: عنوان گروه; HTML: <legend>)
The title of a field set. It must be the first child of the group and is drawn on its border.
field set:
legend: content = "Payment" end
input: name = "card" placeholder = "Card number" end
end
output (Persian: خروجی; HTML: <output>)
The result of a calculation or of the visitor's action, such as the value next to a slider. Screen readers announce changes in it politely. Set its text from a script with dom.SetText.
input: id = "a" type = number value = 2 end
output: for = "a" content = "4" end
| Attribute | What it does | Example |
|---|---|---|
nameنام | A name for the result, for scripts. An output is never sent with the form. [string] | output: name = "total" content = "0" end |
formفرم | The id of the form this result belongs to, when it stands outside that form. [idref] | form: id = "calc" end
output: form = "calc" content = "0" end |
forبرای | The ids of the controls that the result is calculated from, separated by spaces. It only documents the link for assistive technology. Salam checks that every id exists. [idref (list)] | input: id = "w" type = number end
input: id = "h" type = number end
output: for = "w h" content = "0" end |
progress (Persian: پیشرفت; HTML: <progress>)
A bar for how far a task has got, such as an upload. Without value it shows an endless busy animation. For a measurement that is not progress, such as disk use, take meter. Put a text next to it as well, since the bar alone says little to a screen reader.
progress: value = 30 max = 100 end
| Attribute | What it does | Example |
|---|---|---|
valueمقدار | A number from 0 to max: how much is done. Leave it out for an unknown amount. [float] | progress: value = 70 max = 100 end |
maxبیشینه | The number that means finished. The default is 1, so a value of 0.3 is thirty percent. [float] | progress: value = 3 max = 5 end |
meter (Persian: سنجه; HTML: <meter>)
A gauge for a value inside a known range: battery level, a score, how full a disk is. Browsers colour it green, yellow or red from low, high and optimum. It is not for progress: use progress for that.
meter: value = 6 min = 0 max = 10 low = 3 high = 8 optimum = 10 end
| Attribute | What it does | Example |
|---|---|---|
valueمقدار | The current value. It must lie between min and max; otherwise the browser clips it. This is a required attribute. [float] | meter: value = 0.6 end |
maxبیشینه | The top of the range. The default is 1. [float] | meter: value = 70 max = 100 end |
minکمینه | The bottom of the range. The default is 0. [float] | meter: value = 15 min = -10 max = 40 end |
lowحد پایین | The upper limit of the low part of the range. A value below it counts as low. [float] | meter: value = 20 max = 100 low = 30 end |
highحد بالا | The lower limit of the high part of the range. A value above it counts as high. [float] | meter: value = 90 max = 100 high = 80 end |
optimumبهینه | The best value. When it lies in the high part, high values are drawn green; when it lies in the low part, low values are. This is how disk use (low is good) and a score (high is good) look different. [float] | meter: value = 90 max = 100 low = 30 high = 80 optimum = 0 end |
Try it yourself
- Make a contact form with a name, an email, a topic in a
selectand a message in atext areawith a character counter. Show the collected values in a paragraph when it is sent. - In the validation example, add a Repeat password field and report an error until the two passwords are the same. Read both with
dom.GetValue. - Build a small order form with a number of items (
min = 1), a delivery choice in radio buttons and a total shown in anoutput. - Give the Jalali date field a
requiredand see what the browser does when it is empty and when the date is 1405/13/40.