File anatomy
A .mdl file is indentation-based. Lines ending in : open sections, Markdown fills the body, and dot syntax creates compact inline or control elements.
document:
topbar:
.href@href(/)(Home)
hero:
.badge(New)
# Welcome
Write the page in a readable outline.
section:
## Next section
Markdown content lives here.
Keep related content indented under its parent. Blank lines are fine and help prose breathe.
Sections
A section name followed by : becomes an HTML element with a matching mdl-* class. Unknown names are valid and fall back to a div.
hero:
# Welcome
<div class="mdl-hero">
<h1>Welcome</h1>
</div>
Common structural sections include page, topbar, nav, section, hero, grid, card, form, field, actions, and footer.
Common browser and UI sections include table, thead, tbody, tr, th, td, tabs, modal, drawer, toast, canvas, picture, source, template, component, and island.
Classes
Every section receives a stable generated class:
feature-grid:
card@class(feature-card highlighted):
## Fast
<div class="mdl-feature-grid">
<div class="mdl-card feature-card highlighted">
<h2>Fast</h2>
</div>
</div>
Use generated classes for broad styling and @class(...) for page-specific or utility classes.
Inline elements
Dot syntax creates named inline elements:
.badge(beta)
.btn-primary(Choose)
.href@href(/docs.html)(Docs)
.input@type(email)@autocomplete(email)@aria-label(Email)@required
Known names map to useful HTML. For example, buttons become button elements and .href becomes an anchor when you provide @href(...). Unknown names become spans with matching classes.
Attributes
Attributes attach directly to sections or inline elements:
form@id(login)@method(post):
field:
label@for(email):
Email
.input@id(email)@type(email)@autocomplete(email)@aria-label(Email)@placeholder(you@example.com)@required
Boolean attributes use no value. Author classes use @class(...) and are appended after the generated MDL class.
Markdown
Markdown owns prose inside sections. MDL renders headings, paragraphs, lists, blockquotes, code fences, tables, strikethrough, and task lists through CommonMark.
section:
## Markdown works here
- Write lists
- Add `inline code`
- Keep content readable
When a line is not an MDL section, dot element, attribute continuation, or comment, treat it as Markdown prose.
Comments
Use // for source comments that compile to HTML comments:
// This note is visible in the generated HTML source.
section:
## Content
JavaScript owns comments inside script js: blocks, so // there remains JavaScript.
JavaScript
Inline JavaScript uses script js::
script js:
console.log("ready")
Configured module scripts can export handlers for event attributes:
form@submit(handleSignup):
.btn-primary@click(handleSignup)(Join)
Event handlers receive the browser event. @mount(handler) initializes mounted elements and receives the mounted element.
UI patterns
MDL includes named sections for common patterns while CSS and JavaScript own presentation and state:
tabs@id(settingsTabs):
tablist@role(tablist):
.btn-secondary@click(showProfile)(Profile)
.btn-ghost@click(showBilling)(Billing)
tab@id(profileTab)@data-state(active):
Profile content.
tab@id(billingTab):
Billing content.
Escape hatches
MDL is intentionally small, but it still has explicit exits for platform features.
Use element@tag(...) when you need a custom tag:
element@tag(my-widget)@data-mode(compact):
Widget content.
Use raw-html: only for trusted markup that should pass through unchanged:
raw-html:
<custom-element data-ready="true"></custom-element>
Prefer normal MDL sections first; escape hatches are best for interoperability.