You are viewing beta documentation for Formie 4.x.
Templating

Theme config for a design system

If your site uses a design system — Tailwind utilities, Bootstrap components, or shared BEM classes — theme config is usually the best way to align Formie forms with it. You define HTML tags and attributes once, then reuse that configuration across every form.

Prerequisites

Why theme config for design systems

Formie's default markup uses stable formie-* classes and semantic data-formie-* hooks. Theme config lets you:

  • Add framework classes to forms, fields, inputs, and buttons
  • Replace or remove default classes with reset: true
  • Change wrapper tags (for example, <fieldset> for pages)
  • Remove elements entirely by returning false for a slot
  • Target specific field types (singleLineText, repeater, etc.)

You get consistent output without maintaining dozens of Twig overrides.

Understand the form structure

A simplified view of default output:

<form class="formie-form" data-formie-form>
    <div class="formie-pages">
        <div class="formie-page">
            <div class="formie-row">
                <div class="formie-field">
                    <label class="formie-label">Name</label>
                    <div class="formie-field-control">
                        <input type="text">
                    </div>
                </div>
            </div>
        </div>
    </div>
</form>

Each named part — form, field, fieldLabel, fieldInput, submitButton, and many more — is a theme tag you can configure. See the full tag list in Theme Config.

Start with a shared config object

Centralise theme config in a Twig variable or PHP array your templates include:

{# templates/_forms/_theme-config.twig #}

{% set formieThemeConfig = {
    form: {
        attributes: {
            class: 'space-y-8',
        },
    },
    field: {
        attributes: {
            class: 'mb-6',
        },
    },
    fieldLabel: {
        attributes: {
            class: 'block text-sm font-medium text-gray-700 mb-1',
        },
    },
    fieldInput: {
        attributes: {
            class: 'block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500',
        },
    },
    submitButton: {
        attributes: {
            class: 'inline-flex justify-center rounded-md bg-indigo-600 px-4 py-2 text-sm font-semibold text-white hover:bg-indigo-500',
        },
    },
} %}

Use it when rendering any form:

{% include '_forms/theme-config' %}

{{ craft.formie.renderForm('contactForm', {
    themeConfig: formieThemeConfig,
}) }}

Register globally through plugin config

For site-wide defaults, put theme config in config/formie.php. Render-time config and events can still override it:

<?php

return [
    'themeConfig' => [
        'form' => [
            'attributes' => [
                'class' => 'space-y-8',
            ],
        ],
        'field' => [
            'attributes' => [
                'class' => 'mb-6',
            ],
        ],
        'fieldInput' => [
            'attributes' => [
                'class' => 'block w-full rounded-md border-gray-300',
            ],
        ],
    ],
];

Priority (lowest to highest): plugin config → Twig renderForm() → PHP events.

Replace Formie classes entirely

When your design system should not inherit formie-* classes, use reset: true:

{% set formieThemeConfig = {
    form: {
        reset: true,
        attributes: {
            id: 'contact-form',
            class: 'my-form',
        },
    },
    field: {
        reset: true,
        attributes: {
            class: 'form-group',
        },
    },
} %}

Target specific field types

Apply config only to one field type using its camelCase key:

themeConfig: {
    singleLineText: {
        fieldInput: {
            attributes: {
                class: 'input input-bordered w-full',
            },
        },
    },
    checkboxes: {
        fieldOptionLabel: {
            attributes: {
                class: 'label cursor-pointer',
            },
        },
    },
}

Sub-field keys (address1, nameFirst, dateDate, etc.) target inner parts of Address, Name, and Date/Time fields without affecting other inputs.

Conditional classes

Theme config supports structured conditionals — useful when a class should depend on field state:

recipients: {
    field: {
        attributes: {
            class: [
                'recipients-field',
                {
                    if: 'field.isHidden',
                    then: 'hidden',
                },
            ],
        },
    },
},

Available condition context includes form, field, page, currentPage, row, and submission.

Ajax and client-side state

Some UI states change in the browser without a server re-render — tab changes, hidden pages, loading buttons, validation errors. Twig conditionals in themeConfig will not re-evaluate for those updates.

Define root-level semantic class keys at the top level of themeConfig (not inside a slot). Formie embeds resolved classes on data-formie-theme and the browser package toggles them:

KeyWhen applied
pageHiddenPage is not active
tabCurrent / tabLinkCurrentCurrent multi-page tab
tabErrorTab page has field errors
loadingSubmit in progress
fieldLayoutErrorField has errors

Example for Tailwind tab styling:

themeConfig: {
    pageTabLink: {
        attributes: {
            class: ['py-4', 'px-1', 'border-b-2', 'font-medium', 'text-sm'],
        },
    },
    tabLinkInactive: {
        attributes: {
            class: ['border-transparent', 'text-gray-500'],
        },
    },
    tabLinkCurrent: {
        attributes: {
            class: ['border-indigo-500', 'text-indigo-600'],
        },
    },
}

If you use Tailwind JIT, safelist classes defined in PHP config or prefer Twig renderForm() theme config so utilities are discovered at build time.

Use ready-made presets

The Formie theme configs (opens new window) repository includes full Tailwind and Bootstrap examples. Copy a preset into your project and adjust tokens to match your design system rather than starting from scratch.

PHP events for dynamic config

When config must vary by form handle, site, or user role, register theme config through EVENT_MODIFY_SLOT_TAG:

use verbb\formie\elements\Form;
use verbb\formie\events\ModifyFormSlotTagEvent;
use yii\base\Event;

Event::on(Form::class, Form::EVENT_MODIFY_SLOT_TAG, function (ModifyFormSlotTagEvent $event) {
    if ($event->key === 'submitButton') {
        $event->tag->attributes['class'][] = 'btn btn-primary';
    }
});

Field-level events work the same way on Field::EVENT_MODIFY_SLOT_TAG.

When to step up to template overrides

Theme config covers most design-system alignment. Reach for Template Overrides when you need different HTML structure — not just different classes. Reach for Custom Rendering only when you are taking over the entire form output.