Form
Transform Markdown into fully functional, accessible HTML forms. Lists become input fields with types inferred from their names, blockquotes introduce selection groups, and headings create fieldsets.
Basic usage
List items are automatically mapped to the correct input types based on their names.
{% form action="https://formspree.io/f/example" %}
- Name
- Email
- Message
**Send**
{% /form %}<form data-rune="form">
<meta content="https://formspree.io/f/example" data-field="action">
<meta content="POST" data-field="method">
<meta content="" data-field="success">
<meta content="" data-field="error">
<meta content="stacked" data-field="variant">
<meta content="true" data-field="honeypot">
<div data-name="body">
<div data-field="field" data-rune="form-field">
<meta content="text" data-field="field-type">
<div data-name="body">
<label for="field-name">
Name
<span data-name="required" aria-hidden="true">*</span>
</label>
<input type="text" id="field-name" name="field-name" required="">
</div>
</div>
<div data-field="field" data-rune="form-field">
<meta content="email" data-field="field-type">
<div data-name="body">
<label for="field-email">
Email
<span data-name="required" aria-hidden="true">*</span>
</label>
<input type="email" id="field-email" name="field-email" required="">
</div>
</div>
<div data-field="field" data-rune="form-field">
<meta content="number" data-field="field-type">
<div data-name="body">
<label for="field-message">
Message
<span data-name="required" aria-hidden="true">*</span>
</label>
<input type="number" id="field-message" name="field-message" required="">
</div>
</div>
<button type="submit" data-name="submit">Send</button>
</div>
</form><form class="rf-form rf-form--stacked rf-form--https://formspree.io/f/example rf-form--POST rf-form--true" data-variant="stacked" data-action="https://formspree.io/f/example" data-method="POST" data-honeypot="true" data-rune="form" data-density="full">
<div data-name="body" class="rf-form__body" data-section="body">
<div data-field="field" class="rf-form-field rf-form-field--text" data-field-type="text" data-rune="form-field" data-density="full">
<div data-name="body" class="rf-form-field__body">
<label for="field-name">
Name
<span data-name="required" aria-hidden="true">*</span>
</label>
<input type="text" id="field-name" name="field-name" required="" />
</div>
</div>
<div data-field="field" class="rf-form-field rf-form-field--email" data-field-type="email" data-rune="form-field" data-density="full">
<div data-name="body" class="rf-form-field__body">
<label for="field-email">
Email
<span data-name="required" aria-hidden="true">*</span>
</label>
<input type="email" id="field-email" name="field-email" required="" />
</div>
</div>
<div data-field="field" class="rf-form-field rf-form-field--number" data-field-type="number" data-rune="form-field" data-density="full">
<div data-name="body" class="rf-form-field__body">
<label for="field-message">
Message
<span data-name="required" aria-hidden="true">*</span>
</label>
<input type="number" id="field-message" name="field-message" required="" />
</div>
</div>
<button type="submit" data-name="submit" class="rf-form__submit">Send</button>
</div>
</form>Fieldset groups
Headings create <fieldset> sections with legends.
{% form action="/api/contact" %}
## Contact Info
- Name
- Email
- Phone (optional)
## Your Message
- Message
**Submit**
{% /form %}<form data-rune="form">
<meta content="/api/contact" data-field="action">
<meta content="POST" data-field="method">
<meta content="" data-field="success">
<meta content="" data-field="error">
<meta content="stacked" data-field="variant">
<meta content="true" data-field="honeypot">
<div data-name="body">
<fieldset class="rf-form-fieldset">
<legend>Contact Info</legend>
</fieldset>
<div data-field="field" data-rune="form-field">
<meta content="text" data-field="field-type">
<div data-name="body">
<label for="field-name">
Name
<span data-name="required" aria-hidden="true">*</span>
</label>
<input type="text" id="field-name" name="field-name" required="">
</div>
</div>
<div data-field="field" data-rune="form-field">
<meta content="email" data-field="field-type">
<div data-name="body">
<label for="field-email">
Email
<span data-name="required" aria-hidden="true">*</span>
</label>
<input type="email" id="field-email" name="field-email" required="">
</div>
</div>
<div data-field="field" data-rune="form-field">
<meta content="tel" data-field="field-type">
<div data-name="body">
<label for="field-phone">Phone</label>
<input type="tel" id="field-phone" name="field-phone">
</div>
</div>
<fieldset class="rf-form-fieldset">
<legend>Your Message</legend>
</fieldset>
<div data-field="field" data-rune="form-field">
<meta content="number" data-field="field-type">
<div data-name="body">
<label for="field-message">
Message
<span data-name="required" aria-hidden="true">*</span>
</label>
<input type="number" id="field-message" name="field-message" required="">
</div>
</div>
<button type="submit" data-name="submit">Submit</button>
</div>
</form><form class="rf-form rf-form--stacked rf-form--/api/contact rf-form--POST rf-form--true" data-variant="stacked" data-action="/api/contact" data-method="POST" data-honeypot="true" data-rune="form" data-density="full">
<div data-name="body" class="rf-form__body" data-section="body">
<fieldset class="rf-form-fieldset">
<legend>Contact Info</legend>
</fieldset>
<div data-field="field" class="rf-form-field rf-form-field--text" data-field-type="text" data-rune="form-field" data-density="full">
<div data-name="body" class="rf-form-field__body">
<label for="field-name">
Name
<span data-name="required" aria-hidden="true">*</span>
</label>
<input type="text" id="field-name" name="field-name" required="" />
</div>
</div>
<div data-field="field" class="rf-form-field rf-form-field--email" data-field-type="email" data-rune="form-field" data-density="full">
<div data-name="body" class="rf-form-field__body">
<label for="field-email">
Email
<span data-name="required" aria-hidden="true">*</span>
</label>
<input type="email" id="field-email" name="field-email" required="" />
</div>
</div>
<div data-field="field" class="rf-form-field rf-form-field--tel" data-field-type="tel" data-rune="form-field" data-density="full">
<div data-name="body" class="rf-form-field__body">
<label for="field-phone">Phone</label>
<input type="tel" id="field-phone" name="field-phone" />
</div>
</div>
<fieldset class="rf-form-fieldset">
<legend>Your Message</legend>
</fieldset>
<div data-field="field" class="rf-form-field rf-form-field--number" data-field-type="number" data-rune="form-field" data-density="full">
<div data-name="body" class="rf-form-field__body">
<label for="field-message">
Message
<span data-name="required" aria-hidden="true">*</span>
</label>
<input type="number" id="field-message" name="field-message" required="" />
</div>
</div>
<button type="submit" data-name="submit" class="rf-form__submit">Submit</button>
</div>
</form>Selection fields
A blockquote followed by a list creates a selection group. With 4 or fewer options, it renders as radio buttons. Add (multiple) for checkboxes.
{% form action="/api/signup" %}
- Name
- Email
> What interests you? (multiple)
- Web Design
- Branding
- Consulting
- Development
> Preferred contact method
- Email
- Phone
- No preference
**Sign Up**
{% /form %}<form data-rune="form">
<meta content="/api/signup" data-field="action">
<meta content="POST" data-field="method">
<meta content="" data-field="success">
<meta content="" data-field="error">
<meta content="stacked" data-field="variant">
<meta content="true" data-field="honeypot">
<div data-name="body">
<div data-field="field" data-rune="form-field">
<meta content="text" data-field="field-type">
<div data-name="body">
<label for="field-name">
Name
<span data-name="required" aria-hidden="true">*</span>
</label>
<input type="text" id="field-name" name="field-name" required="">
</div>
</div>
<div data-field="field" data-rune="form-field">
<meta content="email" data-field="field-type">
<div data-name="body">
<label for="field-email">
Email
<span data-name="required" aria-hidden="true">*</span>
</label>
<input type="email" id="field-email" name="field-email" required="">
</div>
</div>
<fieldset data-rune="form-field" class="rf-form-choice-group">
<meta data-field="field-type" content="checkbox">
<legend>
What interests you?
<span data-name="required" aria-hidden="true">*</span>
</legend>
<label class="rf-form-choice">
<input type="checkbox" name="field-what-interests-you?" value="Web Design">
<span>Web Design</span>
</label>
<label class="rf-form-choice">
<input type="checkbox" name="field-what-interests-you?" value="Branding">
<span>Branding</span>
</label>
<label class="rf-form-choice">
<input type="checkbox" name="field-what-interests-you?" value="Consulting">
<span>Consulting</span>
</label>
<label class="rf-form-choice">
<input type="checkbox" name="field-what-interests-you?" value="Development">
<span>Development</span>
</label>
</fieldset>
<fieldset data-rune="form-field" class="rf-form-choice-group">
<meta data-field="field-type" content="radio">
<legend>
Preferred contact method
<span data-name="required" aria-hidden="true">*</span>
</legend>
<label class="rf-form-choice">
<input type="radio" name="field-preferred-contact-method" value="Email" required="">
<span>Email</span>
</label>
<label class="rf-form-choice">
<input type="radio" name="field-preferred-contact-method" value="Phone">
<span>Phone</span>
</label>
<label class="rf-form-choice">
<input type="radio" name="field-preferred-contact-method" value="No preference">
<span>No preference</span>
</label>
</fieldset>
<button type="submit" data-name="submit">Sign Up</button>
</div>
</form><form class="rf-form rf-form--stacked rf-form--/api/signup rf-form--POST rf-form--true" data-variant="stacked" data-action="/api/signup" data-method="POST" data-honeypot="true" data-rune="form" data-density="full">
<div data-name="body" class="rf-form__body" data-section="body">
<div data-field="field" class="rf-form-field rf-form-field--text" data-field-type="text" data-rune="form-field" data-density="full">
<div data-name="body" class="rf-form-field__body">
<label for="field-name">
Name
<span data-name="required" aria-hidden="true">*</span>
</label>
<input type="text" id="field-name" name="field-name" required="" />
</div>
</div>
<div data-field="field" class="rf-form-field rf-form-field--email" data-field-type="email" data-rune="form-field" data-density="full">
<div data-name="body" class="rf-form-field__body">
<label for="field-email">
Email
<span data-name="required" aria-hidden="true">*</span>
</label>
<input type="email" id="field-email" name="field-email" required="" />
</div>
</div>
<fieldset class="rf-form-field rf-form-field--checkbox rf-form-choice-group" data-field-type="checkbox" data-rune="form-field" data-density="full">
<legend>
What interests you?
<span data-name="required" aria-hidden="true">*</span>
</legend>
<label class="rf-form-choice">
<input type="checkbox" name="field-what-interests-you?" value="Web Design" />
<span>Web Design</span>
</label>
<label class="rf-form-choice">
<input type="checkbox" name="field-what-interests-you?" value="Branding" />
<span>Branding</span>
</label>
<label class="rf-form-choice">
<input type="checkbox" name="field-what-interests-you?" value="Consulting" />
<span>Consulting</span>
</label>
<label class="rf-form-choice">
<input type="checkbox" name="field-what-interests-you?" value="Development" />
<span>Development</span>
</label>
</fieldset>
<fieldset class="rf-form-field rf-form-field--radio rf-form-choice-group" data-field-type="radio" data-rune="form-field" data-density="full">
<legend>
Preferred contact method
<span data-name="required" aria-hidden="true">*</span>
</legend>
<label class="rf-form-choice">
<input type="radio" name="field-preferred-contact-method" value="Email" required="" />
<span>Email</span>
</label>
<label class="rf-form-choice">
<input type="radio" name="field-preferred-contact-method" value="Phone" />
<span>Phone</span>
</label>
<label class="rf-form-choice">
<input type="radio" name="field-preferred-contact-method" value="No preference" />
<span>No preference</span>
</label>
</fieldset>
<button type="submit" data-name="submit" class="rf-form__submit">Sign Up</button>
</div>
</form>Style variants
Use the variant attribute to change the layout.
{% form action="/api/subscribe" variant="inline" %}
- Email (placeholder: "[email protected]")
**Subscribe**
{% /form %}<form data-rune="form">
<meta content="/api/subscribe" data-field="action">
<meta content="POST" data-field="method">
<meta content="" data-field="success">
<meta content="" data-field="error">
<meta content="inline" data-field="variant">
<meta content="true" data-field="honeypot">
<div data-name="body">
<div data-field="field" data-rune="form-field">
<meta content="email" data-field="field-type">
<div data-name="body">
<label for="field-email">
Email
<span data-name="required" aria-hidden="true">*</span>
</label>
<input type="email" id="field-email" name="field-email" placeholder="[email protected]" required="">
</div>
</div>
<button type="submit" data-name="submit">Subscribe</button>
</div>
</form><form class="rf-form rf-form--inline rf-form--/api/subscribe rf-form--POST rf-form--true" data-variant="inline" data-action="/api/subscribe" data-method="POST" data-honeypot="true" data-rune="form" data-density="full">
<div data-name="body" class="rf-form__body" data-section="body">
<div data-field="field" class="rf-form-field rf-form-field--email" data-field-type="email" data-rune="form-field" data-density="full">
<div data-name="body" class="rf-form-field__body">
<label for="field-email">
Email
<span data-name="required" aria-hidden="true">*</span>
</label>
<input type="email" id="field-email" name="field-email" placeholder="[email protected]" required="" />
</div>
</div>
<button type="submit" data-name="submit" class="rf-form__submit">Subscribe</button>
</div>
</form>Attributes
| Attribute | Type | Default | Description |
|---|---|---|---|
action | string | — | Form submission endpoint URL (required) |
method | string | POST | HTTP method: GET or POST |
success | string | — | Message shown on successful submission |
error | string | — | Message shown on failed submission |
variant | string | stacked | Layout: stacked, inline, compact |
name | string | — | Form identifier for multi-form pages |
honeypot | boolean | true | Auto-generate honeypot spam field |
Smart type inference
Field types are automatically inferred from the list item text:
| Field name contains | Input type |
|---|---|
<input type="email"> | |
| phone, mobile, tel | <input type="tel"> |
| website, url | <input type="url"> |
| date, birthday | <input type="date"> |
| number, amount, quantity | <input type="number"> |
| password, pin | <input type="password"> |
| message, comments, description | <textarea> |
| file, upload, attachment | <input type="file"> |
| anything else | <input type="text"> |
Common attributes
All block runes share these attributes for layout and theming.
| Attribute | Type | Default | Description |
|---|---|---|---|
width | string | content | Page grid width: content, wide, or full |
spacing | string | — | Vertical spacing: flush, tight, default, loose, or breathe |
inset | string | — | Horizontal padding: flush, tight, default, loose, or breathe |
tint | string | — | Named colour tint from theme configuration |
tint-mode | string | auto | Colour scheme override: auto, dark, or light |
bg | string | — | Named background preset from theme configuration |
Field modifiers
Add parenthetical modifiers to list items:
(optional)— removes the required attribute(placeholder: "hint text")— adds placeholder text
Example: - Phone (optional, placeholder: "+1 555-0100")