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>
Contact Info
Your Message
<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>
What interests you?
Preferred contact method
<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

AttributeTypeDefaultDescription
actionstringForm submission endpoint URL (required)
methodstringPOSTHTTP method: GET or POST
successstringMessage shown on successful submission
errorstringMessage shown on failed submission
variantstringstackedLayout: stacked, inline, compact
namestringForm identifier for multi-form pages
honeypotbooleantrueAuto-generate honeypot spam field

Smart type inference

Field types are automatically inferred from the list item text:

Field name containsInput type
email<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.

AttributeTypeDefaultDescription
widthstringcontentPage grid width: content, wide, or full
spacingstringVertical spacing: flush, tight, default, loose, or breathe
insetstringHorizontal padding: flush, tight, default, loose, or breathe
tintstringNamed colour tint from theme configuration
tint-modestringautoColour scheme override: auto, dark, or light
bgstringNamed 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")