Markdown to amazing forms and web pages

blocks.md is a tool that takes your Markdown files and turns them into forms and web pages that are beautiful, customizable, accessible, and fully localizable.

name* = TextInput(
	| question = What is your name?
	| description = 
		Let's get started with the survey. First, please tell
		us your full legal name according to your passport.
)

---
# Hey {$ name $} 👋

It's a **pleasure** to meet you. Let us continue.

---
email* = EmailInput(
	| question = What is your email address?
	| description =
		Please enter an email address where we can reach you
		for a reply. Make sure to avoid spelling mistakes.
)

Logic jumps and page progress

Add -> {condition} to a slide to conditionally show it, that is, the slide will be shown to the user only if the jump condition is true. This is called a logic jump. Also use |> {percentage|fraction} to show page progress on top. This indicates how much of the form has been completed so far.

Further reading: Slides - Docs

specialty* = ChoiceInput(
	| question = What is your specialty?
	| description = 
		This will help us with your placement on our team.
	| choices = Back-end, Front-end, Full-stack
)

---
-> specialty == "Back-end"
|> 50%

At this stage, we're only hiring engineers who have front-end 
expertise. Sorry for the inconvenience.

---
-> specialty == "Front-end" or specialty == "Full-stack"
|> 1/2

expertise* = SelectBox(
	| question = What is your level of expertise?
	| description = 
		Choose the level of experience you have in the 
		industry. Again, this will help with your placement.
	| options = Junior, Mid-level, Senior
)

Send full and partial responses to your server or Google Sheets

Set the #! post-url = {url} setting to send form responses to that URL using a POST request. There's also an incredibly useful integration that lets you send responses directly to Google Sheets. Responses are sent to the {url} when the user reaches the end slide.

Partial submissions are also supported. Add >> post (case insensitive) to a slide to enable partial submission up to that slide, that is, when a user completes this slide and goes to the next one, all the form data up to that slide will be sent to the URL you set in the setting. See video example.

Further reading: Send responses - Docs

#! post-url = {url}
>> post

phone* = TelInput(
	| question = What is your phone number?
	| description = 
		This will allow us to get in touch and fulfill your 
		order. Make sure the number is correct.
)

---
products* = PictureChoice(
	| question = What products would you like?
	| choices = 
		T-Shirts && https://res.cloudinary.com/dnriuttuy/image/upload/v1720015194/tshirts.png,
		Socks && https://res.cloudinary.com/dnriuttuy/image/upload/v1720015163/socks.png
	| multiple
)

Collect data of all types

Use different types of inputs to collect all the data you need, ranging from basic types like text, email, and choice inputs to advanced types such as rating, opinion scales (including Net Promoter Score®), etc. More advanced fields are being worked on currently, particularly date time input, file/image upload, payments, etc.

Further reading: Form fields - Docs

rating* = RatingInput(
  | question = How would you rate your experience?
  | description = 
    Rate your shopping experience so far. This will help us 
    improve our service in the future.
)

---
-> rating >= 4

# Thank you!

Thanks for rating us so positively. Your support is greatly 
appreciated by our entire team.

---
opinion* = OpinionScale(
  | question = 
    How likely are you to recommend our product to a friend 
    or colleague?
)

Class names and attributes

Add class names and other attributes (such as IDs, WAI-ARIA tags, etc.) to any block-level element by putting them inside [...] and placing this at the very start of the element's syntax. IDs start with #, class names start with ., and other attributes can be added as-is, for example, style="...".

A lot of CSS utility classes are included by default, but you can also easily use Tailwind CSS (or any other similar framework) if you need more control.

Further reading: Class names and attributes - Docs

# [#title .anchored] Company profile

[style="font-size: 17px;"]
Welcome to our company profile where you can find out about 
our values, offices, and the services we provide.

- [.col-6 .xs:col-6 .list-inside]
- Mission
- Office guide

0. [.col-6 .xs:col-6 .list-inside]
1. London
2. New York

[.col-6 .mt-3]
email* = EmailInput(
	| question = Subscribe
	| subfield
	| fieldSize = sm
)

Data-binding, content division and content span

Create wrapping <div> elements by putting content inside pairs of :::. The content inside can be any valid Markdown, such as headings, paragraphs, lists, form fields, etc. Class names and other attributes are supported via [...] (see above). Moreover, you can bind one or more fields to a <div> element by adding the names of the fields inside {$...$} (separated by spaces), and placing this within the [...]. This means that whenever the value of a binded field changes, the content inside the <div> will be automatically re-rendered.

The templating is done using Nunjucks, so its entire list of features such as if-else statements, loops, filters, etc. are fully supported. You can also bind a single field to a <span> element using {$ field $}. This is obviously not as flexible, but it is simple, and can be used inside paragraphs, headings, list items, etc.

Further reading: Data-binding - Docs

product* = SelectBox(
	| question = Product | options = T-Shirts, Socks 
	| selected = T-Shirts | subfield
)

price* = NumberInput(
	| question = Price | unitEnd = $ | subfield | min = 1
)

quantity* = NumberInput(
	| question = Quantity | subfield | min = 1
)

[.col-4]
Selected: {$ product $}

::: [.col-8 .text-end .xs:text-start {$ price quantity $}]
{% if price and quantity -%}
	Total: ${{ price }} × {{ quantity }} = ${{ price * quantity }}
{% else -%}
	Total: Set price and quantity
{% endif %}
:::

Highly customizable, including branding

Configure a setting by adding #! {name} = {value}, so you can change things like the color, background-color, accent, etc. You can also add branding, call to action (CTA), change page layout and alignment, and so much more. Moreover, you can remove our branding too using #! blocksmd-branding = hide as soon as you buy a license, so the page becomes entirely your own.

Some settings (like the colors and brand) can accept up to two values in the format #! {name} = {value1} || {value2}. The first value is used for the initial color scheme (which is light by default), and the second value is used for the alternate or dark color scheme. If you change the default color scheme to dark by setting #! color-scheme = dark, then the first value would be used in dark mode, and the second value would be used in light mode.

Further reading: Settings - Docs

#! accent = #333333 || #dfdfdf
#! accent-foreground = #dfdfdf || #333333
#! background-color = rgb(255, 255, 255) || rgb(26, 26, 26)
#! blocksmd-branding = hide
#! brand = ![Logo](https://res.cloudinary.com/dnriuttuy/image/upload/v1721059180/logo-lm.svg) || ![Logo](https://res.cloudinary.com/dnriuttuy/image/upload/v1721059180/logo-dm.svg)
#! color = #333333 || #dfdfdf
#! cta = [Sign up](https://example.com)
#! page-progress = hide
#! rounded = none

plant* = PictureChoice(
	| question = What type of plant would you like?
	| choices = 
		House plants && https://res.cloudinary.com/dnriuttuy/image/upload/v1721060655/hplants.jpg,
		Office plants && https://res.cloudinary.com/dnriuttuy/image/upload/v1721060651/oplants.jpg
)

Set and read data, create reactive blog posts and articles

Set local data that can be used in the template by putting valid JSON inside pairs of ``` or ~~~, with the data keyword placed right after the opening pair. You can also read data from a URL using #! get-url = {url}. By default, CSV is the expected format, and the incoming data is formatted so that each key is a spreadsheet cell reference. However, JSON is also supported.

Moreover, you can set #! page = single to create a normal webpage that is not a form. This way, you can create blog posts and articles which are reactive, and change depending on the data. In this example for instance, the data is coming in from this spreadsheet, and the author would only need to turn off cell D3 to show that they are not looking for work. Learn how to connect with Google Sheets.

Further reading: Set and read data - Docs

#! get-url = https://docs.google.com/spreadsheets/d/e/2PACX-1vTKJSALN8U91YSqvQZ6bQf24z0okzMM2J9D2VtptW2eASFbIC9dKyj2SlSpeaozczNR-u15mfpHqjuV/pub?gid=0&single=true&output=csv
#! page = single

``` data
{
	"name": "John Doe",
	"email": "john@example.com"
}
```

# {{ name }}

I'm a {{ A3 }} with {{ C3 }}+ years of experience, with a 
strong focus on building scalable microservices. I'm 
proficient in the following languages: 

- [.list-unstyled]
- [x] Python
- [x] JavaScript/TypeScript
- [x] Go

---
{% if D3 -%}
	I'm currently available for work: {{ email }}.
{% else -%}
	I'm currently not available for work.
{% endif %}

Localizable to any language

Set the #! localization = {lang} to a supported language code, and write your Markdown in that language—everything will be automatically translated. Can't find the language of your choice in that list? It is remarkably easy to add support for a new language.

Further reading: Localize - Docs

#! localization = es

name* = TextInput(
	| question = Cómo te llamas?
	| description = 
		Empecemos con la encuesta. En primer lugar, dinos tu 
		nombre legal completo según tu pasaporte.
)

---
# Hola {$ name $} 👋

Es un **placer** conocerte. Continuemos.

---
email* = EmailInput(
	| question = Cuál es tu dirección de correo electrónico?
	| description =
		Introduzca una dirección de correo electrónico en la 
		que podamos localizarle para responderle.
)

Try for free, buy when you're ready

blocks.md is a very powerful tool that can help you do a lot, while still being easy to use. It already has a ton of features to help you build amazing forms and web pages, and a lot more are on the way:

  1. Date time input
  2. Multi-choice input with dynamic filtering and results
  3. Rating input Done
  4. Opinion scale and Net Promoter Score® Done
  5. File/image upload
  6. Ranking input
  7. Honeypot fields for spam prevention
  8. Layout options with images and media
  9. Classic form style
  10. Payments
  11. WordPress plugin Done
  12. Cloud version
  13. Go open-source

And so on. Our license allows you to use it for free forever without creating an account, as long as it is on local. You only need to buy the software when you're ready to use it on your production website.

npm install blocksmd

Pricing

Ready to buy? Select the license you want to purchase.

Standard license price
$59.00/once
plus local taxes
Number of end products 1
Use for personal or client
Unlimited form responses
Email support
Free updates to any 1.x version
Remove blocks.md branding
Integrate into product
Multisite license price
$199.00/once
plus local taxes
Number of end products
Use for personal or client
Unlimited form responses
Email support
Free updates to any 1.x version
Remove blocks.md branding
Integrate into product
Enterprise license price Varies
Number of end products 1
Use for personal or client
Unlimited form responses
Dedicated support
Free updates
Remove blocks.md branding
Integrate into product
If you have any questions, send us an email or book a call.