---
title: A rich, flexible article body
description: Let editors build a rich body out of typed blocks, paragraph, quote, image, text-with-image, using a single choice field.
updated: 2026-07-25
---

A `choice` field holds an ordered list of blocks, where each block is one of several **typed variants** the editor picks from. It is how you get an article-style body whose pieces are structured data rather than one lump of HTML.

<div class="q-callout q-callout-note"><i class="fa-solid fa-flask lead"></i>
  <div><b>Live example: Méridienne.</b> Every snippet on this page comes from <b>Méridienne</b>, the demo site of a neighbourhood coffee roaster. Its <code>cafe</code> collection stores each entry's story in a <code>choice</code> field named <code>histoire</code>, with four variants.</div></div>

## 1. Check that you need one

`choice` is the heaviest of the three "rich body" options. Reach for it last.

| Need | Use |
|---|---|
| Styled prose: headings, bold, links, bullet lists, a pull quote from the style palette | `wysiwyg` |
| One repeating shape, always the same fields | `repeat` |
| Several **different** shapes, interleaved in an order the editor chooses | `choice` |

The test is heterogeneity. A body that is prose with the occasional inline image is a `wysiwyg`. A body that alternates paragraphs, full-width images with captions, pull quotes and side-by-side text-plus-image is a `choice`.

## 2. Declare the variants

Each entry under `config.choices` is one variant: a key, a `name` shown in the editor, and its own `fields`.

```yaml
histoire:
  type: choice
  label: Histoire
  config:
    choices:
      paragraph:
        name: Texte
        fields:
          text:
            type: wysiwyg
            label: Texte
      textImage:
        name: Texte + image
        fields:
          text:
            type: wysiwyg
            label: Texte
          image:
            type: image
            label: Image
            config:
              versions:
                wide:
                  label: Large
                  width: 1200
                  height: 900
          side:
            type: select
            label: Côté de l'image
            config:
              options:
                gauche: Gauche
                droite: Droite
      quote:
        name: Citation
        fields:
          text:
            type: textarea
            label: Citation
          author:
            type: text
            label: Auteur
      image:
        name: Image
        fields:
          image:
            type: image
            label: Image
            config:
              versions:
                wide:
                  label: Large
                  width: 1400
                  height: 900
          caption:
            type: text
            label: Légende
```

Variants are independent: two of them may declare a field with the same name and different types, and nothing is shared between them. Keep the set small, four or five variants is a comfortable ceiling, and give each one a `name` an editor understands without training.

<div class="q-callout q-callout-tip"><i class="fa-solid fa-lightbulb lead"></i>
  <div><b>Give it its own tab.</b> Méridienne puts <code>histoire</code> in a dedicated <code>histoire</code> tabset, away from the entry's factual fields. A body builder next to a price field makes for a crowded form.</div></div>

## 3. Know the discriminator

Every stored block carries a `_choice` key holding the variant key it was created from. That value is how a template knows what it is looking at.

```json
[
  { "_choice": "paragraph", "text": "<p>…</p>" },
  { "_choice": "quote", "text": "…", "author": "Ana Ruiz" },
  { "_choice": "textImage", "text": "<p>…</p>", "image": [{ "…": "…" }], "side": "droite" }
]
```

The key is `_choice`, not `_type`.

## 4. Write one template per variant

Put them under a folder of their own, one file per variant key, and let each read the block it was handed:

```liquid
{%- comment -%} elements/blocs/quote.liquid — arg: bloc {%- endcomment -%}
<blockquote>
  <p>« {{ bloc.text }} »</p>
  {% if bloc.author != blank %}<footer>— {{ bloc.author }}</footer>{% endif %}
</blockquote>
```

```liquid
{%- comment -%} elements/blocs/textImage.liquid — arg: bloc {%- endcomment -%}
{% assign img = bloc.image[0].versions.wide.url %}
{% assign imgRight = false %}
{% if bloc.side == 'droite' %}{% assign imgRight = true %}{% endif %}
<div class="grid md:grid-cols-2">
  {% if img %}<div class="{% if imgRight %}md:order-2{% endif %}" style="background-image:url('{{ img }}')"></div>{% endif %}
  {% if bloc.text != blank %}<div class="{% if imgRight %}md:order-1{% endif %}">{{ bloc.text }}</div>{% endif %}
</div>
```

## 5. Dispatch on the discriminator

One small partial turns the variant key into a filename. It is the same trick the content-block dispatcher uses, applied one level down:

```liquid
{%- comment -%} _choice.liquid — arg: blocs (the choice field's value) {%- endcomment -%}
{% for bloc in blocs %}
  {% if bloc._choice != blank %}
    {% assign blocFile = 'elements/blocs/' | append: bloc._choice | append: '.liquid' %}
    {% include blocFile %}
  {% endif %}
{% endfor %}
```

Because `include` shares scope, each variant template sees `bloc` without any further plumbing. Call it once, wherever the body belongs:

```liquid
{% if cafe.histoire != blank %}
  <div class="prose-histoire">
    {% include '_choice', blocs: cafe.histoire %}
  </div>
{% endif %}
```

Adding a fifth variant is then two steps: a new entry under `choices`, a new file under `elements/blocs/`. The dispatcher does not change.

## Pitfalls

- **A missing variant template renders nothing, silently.** The dispatcher builds a filename from data. If an editor picks a variant you have not written a template for, that block disappears from the page with no error. Add the template at the same time as the variant.
- **Do not use `choice` for standard prose.** A body that is only paragraphs and headings belongs in a `wysiwyg`, where the style palette already provides lead paragraphs, quotes and drop caps. Reaching for `choice` there gives the editor a builder for something they could just type.
- **Guard `bloc.image[0]`.** A variant's image field is an array like any other, and an editor can save a block before uploading the image.

## Related

<div class="q-cards">
  <a class="q-card reco" href="/en/docs/custom-fields"><span class="k">Recommended</span><span class="t">Custom fields</span><span class="d">The <code>choice</code> config, and every other field type.</span></a>
  <a class="q-card" href="/en/docs/reuse-partials"><span class="t">Reuse blocks (partials)</span><span class="d">Why the dynamic include works.</span></a>
  <a class="q-card" href="/en/docs/content-model"><span class="t">The content model</span><span class="d">Where a field sits among pages, blocks and collections.</span></a>
</div>
