Primitives
Reference all 14 SML block primitives plus inline strong, em, and a tags, with attributes and content models.
Every SML document is a tree of primitives rooted at <Email>. The parser enforces
which tags may nest where (the content model), putting a <Text> directly inside
another <Text>, or a bare string outside any block, is a compile error naming the
fix. There are 14 block tags and 3 inline tags; that's the whole vocabulary in v1.
Document structure
<Email>
<Preview> , optional, must be first child
<Container> , block children
<Section> , block children
<Row> , <Column> children only
<Column> , block children
<Heading>, <Text>, inline content (text, {{vars}}, strong/em/a)
<Button> , inline content, no nested links
<Image>, <Divider>, <Spacer>, void (self-closing)
<If>, <Else> , block children; <Else> only as <If>'s last childBlock tags
<Email>
The document root, exactly one per file, and it must be the first and only top-level element.
| Attribute | Type | Required | Notes |
|---|---|---|---|
lang | string | no | HTML lang attribute. |
dir | string | no | HTML dir attribute (ltr/rtl). |
class | string | no | See the class-subset reference. |
Content model: an optional <Preview> first, then any block tag.
<Email lang="en" dir="ltr" class="bg-stone-50">
...
</Email><Preview>
The hidden preheader text shown in inbox lists, next to the subject line. Must be the
first child of <Email> if present.
No attributes. Content model: text and {{variables}} only.
<Preview>Welcome aboard, {{firstName}}, let's get you set up</Preview>The compiler pads this to roughly 200 characters with zero-width characters so trailing body text doesn't leak into the preview in clients that show more than the preheader.
<Container>
The outer content wrapper, typically one per email, holding the max-width and background for the whole message body.
| Attribute | Type | Required | Notes |
|---|---|---|---|
class | string | no | Commonly bg-white, max-w-[600px], padding. |
Content model: block children only (<Section>, <Row>, <Heading>, <Text>,
<Button>, <Image>, <Divider>, <Spacer>, or another <Container>).
<Section>
A generic block-level grouping, background color, padding, or a visual divider between groups of content.
| Attribute | Type | Required | Notes |
|---|---|---|---|
class | string | no |
Content model: block children (same set as <Container>).
<Row> and <Column>
Multi-column layout, the only way to lay content out side by side in SML (there is
no flex/grid; see the class-subset reference for why).
<Row> accepts <Column> children only:
| Attribute | Type | Required | Notes |
|---|---|---|---|
class | string | no |
<Column> takes block children, same as <Container>:
| Attribute | Type | Required | Notes |
|---|---|---|---|
class | string | no | |
width | string | no | e.g. "50%", a literal width string, not a utility class. |
<Row>
<Column width="50%" class="pr-2">
<Text>Plan: {{plan}}</Text>
</Column>
<Column width="50%" class="pl-2">
<Text>Seats: {{seats}}</Text>
</Column>
</Row><Heading>
A heading, compiles to h1–h6 based on level.
| Attribute | Type | Required | Notes |
|---|---|---|---|
level | number | no | 1–6. |
class | string | no |
Content model: inline, text, {{variables}}, and <strong>/<em>/<a>.
<Heading level={1} class="text-3xl font-bold text-gray-900 sm:text-4xl">
Welcome, {{firstName}}
</Heading><Text>
A paragraph of body copy, the most common leaf block.
| Attribute | Type | Required | Notes |
|---|---|---|---|
class | string | no |
Content model: inline, text, {{variables}}, and <strong>/<em>/<a>.
<Button>
A call-to-action link, compiled with the Outlook button hack (mso-padding-alt plus
conditional-comment hairspace runs) so padding renders correctly in classic Outlook.
something raw <a> styling can't achieve.
| Attribute | Type | Required | Notes |
|---|---|---|---|
href | string | yes | |
class | string | no |
Content model: inline, but without links, a button's label can't itself contain
<a> (links can't nest inside links or buttons).
<Button href="{{ctaUrl}}" class="bg-blue-600 text-white font-semibold px-6 py-3 rounded-md">
Open dashboard
</Button><Image>
A self-closing image, reset for consistent cross-client rendering (display:block,
no border/outline, alt always present even if empty).
| Attribute | Type | Required | Notes |
|---|---|---|---|
src | string | yes | |
alt | string | no | Always emitted, even as "", never left undefined. |
width | number | no | 0–10,000. |
height | number | no | 0–10,000. |
class | string | no |
<Image src="https://example.com/logo.png" alt="Samva" width={120} height={40} /><Divider>
A self-closing horizontal rule, compiled as a bordered <hr> reset.
| Attribute | Type | Required | Notes |
|---|---|---|---|
class | string | no |
<Spacer>
A self-closing fixed-height gap, the reliable way to add vertical space (margin is warn-tier and stripped by some clients; see the class-subset reference).
| Attribute | Type | Required | Notes |
|---|---|---|---|
height | number | yes | 1–2,000 (pixels). |
<Spacer height={24} /><If> and <Else>
Send-time conditional branching, the only control flow SML has. <If> evaluates
its test predicate per recipient; <Else>, if present, must be the last child
of <If>. See Conditionals for the full predicate
grammar, send-time-vs-build-time semantics, and how branches survive into the
compiled artifact.
| Attribute | Type | Required | Notes |
|---|---|---|---|
test | string | yes (<If> only) | A predicate over flat variable names, see Conditionals. |
Content model: <If> accepts the same block children as <Container> (including
nested <If>), plus at most one <Else> as its last child. <Else> accepts the
same block children as <If>, minus another <Else>.
<If test="plan == 'pro' and seats > 5">
<Text>Your team plan covers {{seats}} seats.</Text>
<Else>
<Text>Thanks for being a pro customer, {{firstName}}.</Text>
</Else>
</If>Inline tags
These appear only inside inline content (<Heading>, <Text>, <Button>), never
as top-level block children.
| Tag | Attributes | Notes |
|---|---|---|
<strong> | none | Bold emphasis. |
<em> | none | Italic emphasis. |
<a> | href (required) | Cannot nest inside another <a> or inside <Button>. |
<Text>
Thanks for joining <strong>Samva</strong>. Your workspace <em>{{workspace}}</em> is
ready, read the <a href="https://example.com/docs">getting started guide</a> to begin.
</Text>Void tags need self-closing syntax
<Image>, <Divider>, and <Spacer> never take children, write them
self-closing (<Spacer height={24} />). Writing <Spacer height={24}></Spacer> or
leaving off the trailing / is a compile error (void-needs-self-close); see the
error catalog.