The UI Vocabulary
Mar frontends contain no HTML, no CSS, and no SwiftUI. A view function composes elements from a fixed vocabulary, and each platform’s runtime renders that description natively: careful HTML and CSS on the web, real SwiftUI on iOS. This chapter explains the vocabulary, and then defends the controversial part: that it is closed.
Composing a view
view : Model -> View Msg
view model =
navigationStack [ navigationTitle "Sign in" ]
[ form
[ section []
[ text [] "Enter your email and we'll send a code."
, textField [ email, submit Submitted ] "Email" model.draft DraftChanged
]
, section []
[ button [] Submitted "Send me a code" ]
]
]
The grammar has three rules, and you have now seen all of them:
- Elements nest, as function calls building a tree.
- The first argument is always a list of attributes, even when empty (
text []). Adding an attribute never restructures a call. - Interaction points name a
Msg.button [] Submitted "Send me a code"says which message tapping produces;textFieldnames the message its edits carry (DraftChanged, a constructor holding the new text). Views never handle events; they label them.
The vocabulary covers the app staples: navigationStack and navigationTitle for the chrome (with topBarLeading / topBarTrailing slots for bar actions); list, section, header, footer for grouped content; title, subtitle, text, paragraph with span, bold, italic, code, link for words; textField, picker, datePicker, toggle, button for input; vstack, hstack, spacer for arrangement; navigationLink for routes; image; sheet and confirm for modals; list with keys, drag-to-reorder, and swipe-to-delete where a platform supports them.
Each of these is semantic: it says what something is, not how to draw it. That is the whole trick. Because a section is a concept rather than a <div> with classes, the web runtime can render it as a grouped card and the iOS runtime as a native grouped list section, and both look at home.
What you get for giving up pixels
Two platforms from one view, honestly native. Not a web page in a wrapper: the iOS renderer emits SwiftUI, with platform behaviors (large-title collapse, native pickers, swipe gestures) implemented by the platform. The same view function, and neither version reads as a port.
A designed baseline instead of a blank page. Typography scale, spacing, dark mode, focus states, hover behavior on pointer devices and its correct absence on touch devices, safe-area handling on phones: all decided once, in the runtime, by people staring at exactly these widgets. The classic web tax (rebuilding “a decent-looking form” for the nth time) is simply not charged.
Semantic correctness by default. Since the runtime owns the rendering, it owns accessibility hooks, keyboard navigation, and the fiddly interaction details, and fixes to them arrive with the framework rather than being every app’s homework. A bug fixed in the renderer is fixed in every Mar app at once.
Views stay legible. A view function reads as an outline of the screen. There is no style sheet to cross-reference, no cascade to debug, no specificity war. What the function says is what there is.
The cost, stated plainly
You cannot draw outside the lines. If the vocabulary lacks a widget, you cannot drop into HTML for a corner of the page; the escape hatch does not exist (except the canvas, next chapter, which is a different world with different rules). Brand-heavy, design-led products will hit this wall and should probably not choose Mar today. The vocabulary grows deliberately, one well-designed element at a time, rather than by admitting arbitrary markup, because the moment arbitrary markup enters, every guarantee in the previous section quietly leaves.
This is the same trade Mar makes everywhere, applied to pixels: fewer choices, stronger promises. The gamble is that for the target class of app (tools, dashboards, products whose users want clarity more than branding), a genuinely good default beats infinite freedom plus infinite responsibility.
One vocabulary, one reconciler
Under the hood, view output is a description diffed against the previous frame, so only what changed touches the screen. You never manage this. It is worth knowing only because it explains the performance model: view runs freely; the runtime makes it cheap.
For interfaces, that is the whole story. For worlds, there is a second, very different drawing surface.