Skip to main content

Formatting Templates

Freeform offers the most flexible and customizable options for handling the formatting templates for forms. You can simply choose from one of our popular framework examples and add your own CSS to style them further, or you can create your very own formatting templates. If you wish to create your own (and we certainly encourage that), feel free to use an existing sample one as a starting point.

Overview

Forms can be generated inside your front end templates one of 3 different ways:

Formatting Template with Form RenderRecommended

The most common and hastle-free approach is to use the formatting template assigned to the form and load a Form query using the render method in your template. In templates that you want your forms to show up in, you simply just insert one line of code:

{{ freeform.form("myFormHandle").render() }}
  • Your form formatting code is stored in a separate template and can be reused across as many of your forms as you wish (just like an include).
  • Formatting templates are Twig template files and are stored in a separate directory inside your Craft Templates directory, so they can still work nicely with version control.
  • Freeform includes several formatting template examples for you to use or user as a starting point for creating your own.
  • This approach is especially handy when rendering forms that have been attached/related forms to entries or other element types.
  • You can have as many formatting templates as you wish. Just specify in the form builder which formatting template the form should use. You won't need to even adjust the page template the form is placed into. If you wish to override the formatting template for the form at template level, you can also specify the formattingTemplate parameter:
    {{ freeform.form("myFormHandle", {
    formattingTemplate: 'template-name.html'
    }).render() }}
  • You can override most of the template attributes and values with the available template overrides:
    {{ freeform.form("myFormHandle", {
    attributes: {
    novalidate: true,
    row: {
    class: "row",
    },
    },
    fields: {
    "@global": {
    attributes: {
    input: { class: "input-class" },
    label: { class: "label-class" },
    },
    },
    "@dropdown": {
    attributes: {
    input: { "+class": "select fullwidth" },
    },
    },
    "@checkboxes, @multiple-select": {
    instructions: "Select all that apply.",
    },
    ":required": {
    attributes: {
    label: { "+class": "form-required" },
    },
    },
    },
    }).render() }}

Code Your Form Directly in the Template

If you absolutely prefer to do so, you can use a Form query with the renderTag to code your form directly inside the template you need it to load in. This can definitely work against you if you intend on placing the form in more than one page.

  • Your form formatting code is contained directly within the template that you want the form to appear in.

  • No matter what formatting template your form may have assigned to it in the form builder, the form always conforms to the template formatting used in this template.

    {% set form = freeform.form("myFormHandle", {
    attributes: { class: "my-form-class" }
    }) %}

    {{ form.renderTag }}

    {% if form.hasErrors %}
    <div class="freeform-form-errors">
    {{ "Error! Please review the form and try submitting again."|t('freeform') }}
    </div>
    {% endif %}

    {% for row in form %}
    <div class="field-row">
    {% for field in row %}
    <div class="field-container">
    {{ field.render() }}
    </div>
    {% endfor %}
    </div>
    {% endfor %}

    {{ form.renderClosingTag }}
  • For ultimate control, forms can be manually built and even include a mix of render helpers as well:

    {% set form = freeform.form("myFormHandle") %}

    {{ form.renderTag({
    returnUrl: "contact/success",
    disable: ["submitButtons"]
    }) }}

    {% if form.hasErrors %}
    <div class="freeform-form-errors">
    {{ "There was an error submitting this form"|t }}
    </div>
    {% endif %}

    {% set fullName = form.get("fullName") %}
    {% set company = form.get("company") %}
    {% set myDropdown = form.get("myDropdown") %}

    <label>{{ fullName.label }}</label>
    <input name="{{ fullName.handle }}" value="{{ fullName.value }}" />
    {{ fullName.renderErrors() }}

    {{ company.renderLabel() }}
    {{ company.renderInput() }}
    {{ company.renderErrors() }}

    <label>Email Address</label>
    <input name="email" />
    {{ form.get("email").renderErrors() }}

    <label>My Dropdown Field</label>
    <select name="{{ myDropdown.handle }}">
    {% for option in myDropdown.options %}
    <option value="{{ option.value }}" {{ option.value == field.value ? "selected" }}>{{ option.label }}</option>
    {% endfor %}
    </select>

    {# Manual submit buttons #}
    {% set page = form.pages.current %}

    <button {{ page.buttons.submitRenderProps({ 'data-optional': 'attributes here' }) }}>
    Submit
    </button>
    {% if form.pages.current.buttons.back %}
    <button {{ page.buttons.backRenderProps }}>
    Back
    </button>
    {% endif %}
    {% if form.pages.current.buttons.save %}
    <button {{ page.buttons.saveRenderProps }}>
    Save
    </button>
    {% endif %}

    {{ form.renderClosingTag }}

Headless using GraphQL and/or a Javascript framework

Freeform supports headless website architecture making it easy to use JavaScript-based front-end frameworks such as Vue.js, Next.js, React JS and more! Freeform also supports querying form layouts and using mutations to create submissions via GraphQL. Click on any of the links below to learn more:

Sample Templates

Freeform includes a large number of sample formatting templates to choose from. You can use these as a starting point and adjust them to suit your needs, or simply create your own from scratch. The following formatting templates are located in the /vendor/solspace/craft-freeform/packages/plugin/src/templates/_templates/formatting/ directory.

If you wish to create your own modified version, you can copy from the code examples further below, or use a sample formatting template as a starting point and make adjustments to it. You can do this by creating a new template folder and file (e.g. my-custom-template/ and index.twig) inside of your Formatting Templates directory in the specified Craft Templates directory (e.g. /templates/_freeform/).

Template NameTypeDescription
Framework AgnosticA neatly styled basic formatting template.
Framework AgnosticA neatly styled basic dark mode formatting template.
Framework AgnosticA neatly styled basic formatting template that displays labels in the center of the inputs and shrinks them to the top-left when clicked and/or contains a value.
Framework AgnosticA neatly styled basic formatting template that displays one field at a time and smoothly scrolls down to the next question until complete. Similar to how Typeform displays forms. Also a great choice for survey forms.
Framework AgnosticA neatly styled basic formatting template that displays ALL fields for a multipage form for the purpose of reviewing/previewing fields on other pages.
BarebonesA very basic starter template that requires additional CSS to style it.
BarebonesA very basic starter template that requires additional CSS to style it.
Popular FrameworkAn implementation of Bootstrap 5.
Popular FrameworkAn implementation of Bootstrap 5 that uses the dark theme.
Popular FrameworkAn implementation of Bootstrap 5 that uses the floating labels feature.
Popular FrameworkAn implementation of the Tailwind CSS 3 framework.
Popular FrameworkAn implementation of the Foundation 6 framework.
  • Framework Agnostic: Plug-and-play neatly styled basic formatting templates that don't require any frameworks or toolkits, etc.
  • Barebones: Barebones starter templates that require additional CSS to complete the styling.
  • Popular Frameworks: Formatting templates that are set up to automatically work well with popular frameworks.

Template OverridesNew in 5.0+

The form and each field have the ability to control attributes, values and more at the template level. Each one of them is entirely optional. There are several namespaces:

  • attributes - whatever you specify here will be set as an attribute on the form.
  • buttons - controls the output of the Submit button(s).
  • fields - controls the output of all Fields.
  • captchas - add attributes to the main Captcha wrapper automatically inserted by Freeform when using reCAPTCHA or hCaptcha.

Here's a breakdown:

  • attributes - control attributes for the form tag, field rows and success/error banners.
    • "foo": "bar"- unknown attributes - applied directly to the <form> tag.
    • class: "my-form-class" - known attributes - applied directly to the corresponding attribute on the <form> tag.
    • row - applied directly to form <div> rows.
    • success - applied directly to Success banner wrapper div.
    • error - applied directly to Error banner wrapper div.
  • buttons - control overrides for submit buttons
    • attributes - applied to submit button container.
      • container
      • column
      • buttonWrapper
      • submit - applied to the Submit button.
      • back - applied to the Back button.
      • save - applied to the Save button.
    • submitLabel - override the label of the Submit button.
    • backLabel - override the label of the Back button.
    • saveLabel - override the label of the Save button.
  • fields - control overrides for fields.
    • "@global" - apply to all fields.
      • attributes - applied to all fields.
        • container - applied to field wrapper <div> for all fields.
        • input - applied to <input> (and <select>, etc) for all fields.
        • label - applied to <label> for all fields.
        • instructions - applied to instructions <div> for all fields.
        • errors - applied to errors <div> for all fields.
    • ":required" - applied to all fields that are set to be required.
    • ":errors" - applied to all fields that trigger an error upon submit of the form (non-AJAX only).
    • "@fieldtypename" - applied to all fields of this type. Use the field type name prefixed with an @ symbol. Specify multiples with a comma, e.g. "@checkboxes, @radios".
      • attributes
        • container - applied to field wrapper <div> for all fields of this type.
        • input - applied to <input> (and <select>, etc) for all fields of this type.
        • label - applied to <label> for all fields of this type.
        • instructions - applied to instructions <div> for all fields of this type.
        • errors - applied to errors <div> for all fields of this type.
      • value - override the default value for the specified field type(s).
      • label - override the field label for the specified field type(s).
      • instructions - override the field instructions for the specified field type(s).
    • "myFieldHandle" applied to a specific field. Use the field handle name.
      • attributes
        • container - applied to field wrapper <div> for this specific field handle.
        • input - applied to <input> (and <select>, etc) for this specific field handle.
        • label - applied to <label> for this specific field handle.
        • instructions - applied to instructions <div> for this specific field handle.
        • errors - applied to errors <div> for this specific field handle.
      • value - override the default value for the specified field.
      • label - override the field label for the specified field.
      • instructions - override the field instructions for the specified field type.
  • captchas - add attributes to the main Captcha wrapper automatically inserted by Freeform when using reCAPTCHA or hCaptcha.New in 5.1+
    • "foo": "bar"- unknown attributes - applied directly to the <div data-freeform-recaptcha-container> tag.
    • class: "my-form-class" - known attributes - applied directly to the corresponding attribute on the <div data-freeform-recaptcha-container> tag.

Field types and field handles can be mixed together to receive shared attributes. Just specify them like: "@fieldtypename, @anotherfieldtypename, myFieldHandle".

Behavior Syntax

You can control the behavior of attributes by specifying a +, -, or = symbol before the attribute name and wrap it in quotes, e.g. "=class". If no behavior symbol is set, e.g. class: "my-class", the + behavior will apply.

This works for unknown attributes as well, e.g. "my-attribute": "whatever" can be written as "=my-attribute": "something else".

  • The + prefix will add the specified values to existing values for the same attribute set elsewhere.
  • The - prefix will remove the specified values from any existing values for the same attribute set elsewhere.
  • The = prefix will replace all existing values of the same attribute set elsewhere with the ones set here.

For example:

{{ form.render({
fields: {
"@global": {
attributes: {
input: { class: "form-input form-text" },
label: { class: "form-label" },
},
},
":required": {
attributes: {
label: { "+class": "form-required" },
},
},
"@dropdown": {
attributes: {
input: {
"-class": "form-text",
"+class": "form-select",
},
},
},
"@checkbox" : {
attributes: {
input: {
"=class": "form-check",
},
},
},
}
}) }}

In the above example:

  • All field inputs will be given the classes: form-input form-text.
  • Dropdown input fields will remove the form-text class and add form-select, resulting in it having the classes: form-input form-select.
  • The Checkbox input fields will replace the classes: form-input form-text entirely with just form-check.
  • All fields that are set to be required inside the form builder will have the additional class of form-required as well as form-label.

The resulting code might look something like this:

// Each field input would render as:
<label class="form-label" ...>
<input class="form-input form-text" ... />

// Required fields would render with the additional "form-required" class:
<label class="form-label form-required" ...>
<input class="form-input form-text" ... />

// Dropdown fields would render with the "form-text" class removed and the
"form-select" class added:
<label class="form-label" ...>
<select class="form-input form-select" ... />

// Checkbox fields would render with both "form-text" and "form-select"
classes replaced with "form-check":
<label class="form-label" ...>
<input class="form-check" ... /></label></label></label
></label>

Full Example

The example below shows the hierarchy and behavior of each option.

{{ form.render({
attributes: {
novalidate: true,
"data-form": true,
id: "my-form-id",
class: "my-form-class",
row: {
"data-row": true,
class: "form-row",
},
success: {
class: "form-success",
},
error: {
class: "form-error",
},
},
buttons: {
attributes: {
container: { ... },
column: { ... },
buttonWrapper: { ... },
submit: { class: "form-field-button blue" },
back: { class: "form-field-button gray" },
save: { class: "form-field-button blue" },
},
submitLabel: "Send",
backLabel: "Previous",
saveLabel: "Save",
},
fields: {
"@global": {
attributes: {
container: {
"data-container": true,
class: "form-field-wrapper",
},
input: {
"data-field": true,
class: "form-field-input",
},
label: {
class: "form-field-label",
},
instructions: {
class: "form-field-instructions",
},
},
},
":required": {
attributes: {
label: { "+class": "form-field-required" },
},
},
":errors": {
attributes: {
input: { "+class": "form-field-is-invalid" },
},
},
":options": { ... },
":fileUpload": { ... },
":multiValue": { ... },
":noStorage": { ... },
":placeholder": { ... },
"@dropdown": {
attributes: {
container: {
"data-select-container": true,
},
input: {
"+class": "form-field-select fullwidth",
},
},
},
"@checkbox" : {
attributes: {
input: {
"=class": "form-field-check-input",
},
label: {
"=class": "form-field-check-label",
},
},
},
"@signature": {
attributes: {
input: {
"-class": "form-field-input",
"+class": "form-field-button",
},
},
}
"@checkboxes, @radios" : {
attributes: {
input: {
"+class": "form-field-options",
},
},
},
"myFieldHandle": {
value: "John Doe",
label: "First Name",
instructions: "This is a test",
attributes: {
container: {
"data-my-input-container": true,
},
input: {
id: "my-special-id",
"+class": "my-special-class",
},
},
},
"@website, @regex, myFieldHandle" : {
attributes: {
input: {
"+class": "my-special-class",
},
},
},
},
captchas: {
class: ["custom-class", "another-class"],
"data-test": true,
}
}) }}

← Applied directly to the <form> tag.




← Applied directly to various structure of
the form render.


← Apply attributes to the success and error
banners (will not affect AJAX forms).





← Overrides for submit buttons.








← Override submit button labels.



← Overrides for fields.
← Overrides applied globally to all fields.

















← Apply attributes to required fields.




← Apply attributes to fields when an error
is triggered for them. (affects non-AJAX forms).


← Target fields by interfaces they implement:
← Uses options, e.g. Dropdown, Checkboxes, etc.
← Uploads files, e.g. File, File Drag & Drop.
← Contains multiple values, e.g. Checkboxes, Multi-select.
← Does not store data in database, e.g. HTML, Rich Text, Password.
← Contains a placeholder, e.g. Text, Phone, etc.
← Applied globally to this field type.
Use the field type name prefixed with "@".

← Unknown attributes should be wrapped in quotes.


← The "+" prefix adds the specified values to
existing values for same attribute set elsewhere.





← The "=" prefix replaces all existing values of the
same attribute set elsewhere with the ones set here.








← The "-" prefix removes the specified values from any
existing values for the same attribute set elsewhere.



← Apply overrides to multiple field types by
separating with a comma.





← Overrides applied to a specific field. Use the field handle.
← Override a field's value
← Override a field's label
← Override a field's instructions
← Override a field's attributes









← Apply overrides to a mix of multiple field
types and field handles.






← Applied directly to the Captcha <div> wrapper.




Submit Button Customization

Submit buttons are automatically inserted at the end of the form by default. However, you can control their appearance by using the following overrides:

{{ form.render({
buttons: {
attributes: {
container: { ... },
column: { ... },
buttonWrapper: { ... },
submit: { class: "form-field-button blue" },
back: { class: "form-field-button gray" },
save: { class: "form-field-button blue" },
},
submitLabel: "Send",
backLabel: "Previous",
saveLabel: "Save",
},
}) }}

Further Customization

If the override approach above is not enough for your needs, you can use a more manual approach. First, you need to disable the automatic insertion of Submit buttons by applying the disable: ["submitButtons"] parameter to your form. Then, you can use the button render approach.

The following are available:

  • renderSubmit - render a complete Submit button
  • renderBack - render a complete Back button
  • renderSave - render a complete Save button
{{ form.renderTag({
returnUrl: "contact/success",
disable: ["submitButtons"]
}) }}

{# Your fields #}

{% set page = form.pages.current %}

{{ page.buttons.renderSubmit({ 'data-optional': 'attributes here' }) }}
{% if form.pages.current.buttons.back %}
{{ page.buttons.renderBack }}
{% endif %}
{% if form.pages.current.buttons.save %}
{{ page.buttons.renderSave }}
{% endif %}

{{ form.renderClosingTag }}

And finally, for maximum control, you can use a property attributes render instead. The following are available:

  • renderSubmitProps - render the property attributes to attach to your Submit button
  • renderBackProps - render the property attributes to attach to your Back button
  • renderSaveProps - render the property attributes to attach to your Save button
{{ form.renderTag({
returnUrl: "contact/success",
disable: ["submitButtons"]
}) }}

{# Your fields #}

{% set page = form.pages.current %}

<button {{ page.buttons.submitRenderProps({ 'data-optional': 'attributes here' }) }}>
Submit
</button>
{% if form.pages.current.buttons.back %}
<button {{ page.buttons.backRenderProps }}>
Back
</button>
{% endif %}
{% if form.pages.current.buttons.save %}
<button {{ page.buttons.saveRenderProps }}>
Save
</button>
{% endif %}

{{ form.renderClosingTag }}

Captcha PlacementNew in 5.7+

When using visible captchas such as reCAPTCHA v2 Checkbox or Cloudflare Turnstile, Freeform will automatically insert them at the end of the form before the submit buttons. However, if you are manually rendering your form, this could become an issue, as Freeform will likely place the captcha at the end, below the submit buttons.

To workaround this issue, you can place {{ form.renderCaptchas }} wherever you would like the captcha to be shown in the form.

Your template code might look something like this:

{% set page = form.pages.current %}
{% set firstName = form.get("firstName") %}
{% set lastName = form.get("lastName") %}

<label>{{ firstName.label }}</label>
<input name="{{ firstName.handle }}" value="{{ firstName.value }}" />
{{ firstName.renderErrors() }}

<label>{{ lastName.label }}</label>
<input name="{{ lastName.handle }}" value="{{ lastName.value }}" />
{{ lastName.renderErrors() }}

{{ form.renderCaptchas }}

{{ page.buttons.renderSubmit({ 'data-optional': 'attributes here' }) }}

{{ form.renderClosingTag }}

How to Set Attributes

These parameters can be passed to any of the render functions, or done separately before.

{# Set the parameters like this at any time #}
{% do field.setParameters({
attributes: { ... }
}) %}

{# Set parameters during rendering of the form #}
{{ form.render({
attributes: { ... }
}) }}

{# Set parameters during rendering of specific elements #}
{{ field.renderInput({
attributes: { ... }
}) }}

Field Attributes Editor

Freeform allows you to also specify attributes for each field inside the form builder. These can be mixed in with the attributes parameter in your form or field render.

If you were to set up your formatting template to include the following:

{{ form.render({
fields: {
attributes: {
input: {
class: "field",
}
}
}
}) }}

All fields will be given a class with a value of field on the input when rendered.

If you were to also apply a class with a value of input-class on the input in the form builder, the resulting code might look something like this:

// Each field input would render as:
<input class="field" ... />

<input class="field" ... />

// With the exception of that one field where you added "input-class" in the
builder. That would render as:
<input class="field input-class" ... />

<input class="field" ... />

Accessing Attribute Properties

In most cases, attributes will automatically be applied in the right spots by Freeform. However, if you ever need to manually access these properties, you can do as follows...

// Access the complete attribute
{{ form.attributes.whatever|raw }}

// Access a specific attribute set
{{ field.attributes.whatever.get("class") }}

Further reading...

If you have this in your template:

{{ form.renderTag({
attributes: {
row: { class: "freeform-row" },
success: { class: "freeform-form-success" },
}
}) }}

And you place the following in your formatting template:

{{ form.attributes.success|raw }}

It will render as:

class="freeform-form-success"

A more complete example might be:

<div{{ form.attributes.success|raw }}>
<p>{{ form.settings.successMessage | t('freeform') }}</p>
</div>

Which renders as:

<div class="freeform-form-success">
<p>Thank you!</p>
</div>

You could also approach it differently by accessing the class attribute value directly:

{{ form.attributes.success.get("class") }}

Which will render only as:

freeform-form-success

A more complete example might be:

<div class="form-banner {{ form.attributes.success.get("class") }}">
<p>{{ form.settings.successMessage | t('freeform') }}</p>
</div>

Which renders as:

<div class="form-banner freeform-form-success">
<p>Thank you!</p>
</div>

Examples

Check out the examples below to see how the template code will render in your page.

On Form tag and Rows

{{ form.render({
attributes: {
novalidate: true,
"data-my-form": true,
class: "my-form-class",

// You can set default attributes for all rows here
row: {
class: "row",
"data-row": true,
},
}

}) }}

On All Fields

You can also pass down attributes to all fields, or specific field types or fields by their handle. To do that you have to use the @fields attribute.

{{ form.render({
attributes: {
novalidate: true,
row: {
class: "row",
},
},
fields: {
"@global": {
attributes: {
input: {
class: "input-class",
},
container: {
"data-container": true,
},
label: {
class: "label-class",
},
},
},
"@dropdown": {
attributes: {
input: {
class: "select fullwidth",
},
container: {
"data-select-container": true,
},
},
},
"@text": { ... },
"@checkboxes, @multiple-select": {
instructions: "Select all that apply.",
},
"myFieldHandle": {
label: "My Field Label Override",
instructions: "My field instructions override.",
value: "my field value override",
attributes: {
container: {
"data-my-input-container": true,
},
input: {
id: "my-field",
class: "big-text",
},
},
},
":options": { ... },
":required": { ... },
":errors": { ... },
":required, @dropdown, @text, myFieldHandle2": { ... }
},
}) }}

On Field Containers

If you would like to add a certain class and a data-container attribute to the Container HTML element of the field, you would pass them as follows:

{{ field.render({
attributes: {
container: {
class: "certain-class",
"data-container": true,
},
},
}) }}

On All Parts of the Fields

You can pass any amount of attributes to the specific element this way:

{{ field.render({
label: "My Field Label Override",
instructions: "My field instructions override.",
value: "my field value override",
attributes: {
container: {
class: "container",
},
input: {
"data-field": true,
class: "input-element",
},
label: {
class: "label",
},
instructions: {
class: "instructions",
},
error: {
class: "error-block",
},
}
}) }}

On Submit Buttons

You can also edit submit, back and save button attributes and values:

For further customization of the submit buttons, please see the options and instructions for manual implementation.

{{ form.render({
buttons: {
attributes: {
container: {
style: "border: 1px solid red;",
},
column: {
style: "background: #ccc;",
},
buttonWrapper: {
style: "padding: 0 5px;",
},
submit: {
class: "btn btn-primary",
},
back: {
class: "btn btn-secondary",
},
save: {
class: "btn btn-secondary",
}
},
submitLabel: "Send",
backLabel: "Previous",
saveLabel: "Save",
},
}) }}

On Captcha WrapperNew in 5.1+

You can add attributes to the main Captcha wrapper automatically inserted by Freeform when using reCAPTCHA or hCaptcha.

{{ form.render({
captchas: {
class: ["custom-class", "another-class"],
"data-test": true,
}
}) }}

If you need full control over the placement of a visible captcha, please see the Captcha Placement guide.

Override Field ValuesChanged in 5.0+

You can override the value inside text fields, or pre-select a default option for multi-option field types (specify option values in this case).

// Grab the title from the Craft entry slug in the second segment of URL.
// Used just for the "entryTitle" example below.
{% set entry = craft.entries.slug(craft.app.request.getSegment(2)).one() %}

{{ form.renderTag({
fields: {
"entryTitle": {
value: entry.title
},
"stateSelect": {
value: "AZ"
},
"availability": {
value: ["tue", "thu"]
},
"firstName": {
value: currentUser.name
},
}
}) }}

Array Values & Conditionals

You can supply an array of strings to the attribute as well, which can make the code appear cleaner. You can also use conditionals to add attributes conditionally in the array. Any empty, false or null values will not be rendered.

{{ field.render({
attributes: {
label: {
id: "label-for-" ~ field.handle,
class: [
"form-control-label",
field.hasErrors ? "is-invalid has-validation",
field.required ? "required",
field.type == "checkbox" ? "checkbox" : "non-checkbox",
],
},
},
}) }}

Boolean Values

You can pass boolean values to attributes and it will render the standalone attribute if the value is true or null. If explicitly set to false, it won't render at all.

{{ field.render({
attributes: {
label: {
"data-required": false,
"data-is-blue": true,
"data-is-some-data": null,
}
}
}) }}

Automated Formatting Template

The following is a basic example of what your formatting template can look like to generate form display. A starting template like this (along with CSS) can be generated for you by visiting the Formatting Templates section in Freeform Settings.

{# Render the opening form tag #}
{{ form.renderTag({
attributes: {
row: { class: "freeform-row" },
success: { class: "freeform-form-success" },
errors: { class: "freeform-form-errors" },
novalidate: true,
},
buttons: {
attributes: {
container: { class: "freeform-button-container" },
column: { class: "freeform-button-column" },
buttonWrapper: { class: "freeform-button-wrapper" },
submit: { class: "freeform-button-submit" },
back: { class: "freeform-button-back" },
save: { class: "freeform-button-save" },
},
},
fields: {
"@global": {
attributes: {
container: { class: "freeform-column" },
input: {
class: "freeform-input",
},
label: { class: "freeform-label" },
instructions: { class: "freeform-instructions" },
error: { class: "freeform-errors" },
},
},
":required": {
attributes: {
label: { "+class": "freeform-required" },
},
},
":errors": {
attributes: {
input: { "+class": "is-invalid has-validation" },
},
},
"@group": {
attributes: {
label: { "+class": "group-label" },
},
},
"@signature": {
attributes: {
input: { "-class": "freeform-input" },
},
}
},
}) }}

{# Success and error message handling for non-AJAX forms #}
{% if not form.settings.ajax %}
{% if form.submittedSuccessfully %}
<div{{ form.attributes.success|raw }}>
<p>{{ form.settings.successMessage | t('freeform') }}</p>
</div>
{% endif %}
{% if form.hasErrors %}
<div{{ form.attributes.errors|raw }}>
<p>{{ form.settings.errorMessage | t('freeform') }}</p>

{% if form.errors|length %}
<ul>
{% for error in form.errors %}
<li>{{ error }}</li>
{% endfor %}
</ul>
{% endif %}
</div>
{% endif %}
{% endif %}

{# Render page tabs if multi-page #}
{% if form.pages|length > 1 %}
<ul class="freeform-pages">
{% for page in form.pages %}
<li {% if form.currentPage.index == page.index %}class="active"{% endif %}>
{% if form.currentPage.index == page.index %}
<b>{{ page.label }}</b>
{% else %}
{{ page.label }}
{% endif %}
</li>
{% endfor %}
</ul>
{% endif %}

{# Build form rows and fields #}
{% macro renderRows(form, rows) %}
{% for row in rows %}
{% set width = (12 / (row|length)) %}
<div{{ form.attributes.row|raw }}>
{% for field in row %}
{% do field.setParameters({
attributes: {
container: { class: [
"freeform-column-" ~ width,
"freeform-fieldtype-" ~ field.type,
]},
},
}) %}
{% if field.type == "group" %}
<div class="freeform-group">
<label{{ field.attributes.label }}>
{{ field.label }}
</label>
<div>
{{ _self.renderRows(form, field.layout) }}
</div>
</div>
{% else %}
{{ field.render }}
{% endif %}
{% endfor %}
</div>
{% endfor %}
{% endmacro %}

{# Display form field rows and columns #}
{{ _self.renderRows(form, form.rows) }}

{# Render the closing form tag #}
{{ form.renderClosingTag }}

Manual Formatting Template

Form formatting can also be extremely manual, if that is something you prefer. Here's an example of different levels of manual you can use:

{% set form = freeform.form("myForm") %}

{{ form.renderTag({
returnUrl: "contact/success",
disable: ["submitButtons"]
}) }}

{% if form.hasErrors %}
<div class="freeform-form-has-errors">
{{ "Error! Please review the form and try submitting again."|t('freeform') }}
</div>
{% endif %}

{% set firstName = form.get("firstName") %}
{% set lastName = form.get("lastName") %}
{% set company = form.get("company") %}
{% set state = form.get("state") %}

<label>{{ firstName.label }}</label>
<input name="{{ firstName.handle }}" value="{{ firstName.value }}" />
{{ firstName.renderErrors() }}

<label>{{ lastName.label }}</label>
<input name="{{ lastName.handle }}" value="{{ lastName.value }}" />
{{ lastName.renderErrors() }}

{{ company.renderLabel() }}
{{ company.renderInput() }}
{{ company.renderErrors() }}

<label>Email Address</label>
<input name="email" />
{{ form.get("email").renderErrors() }}

<label>Phone</label>
<input name="phone" />
{% if form.get("phone").hasErrors %}
This field is required!
{% endif %}

<label>State</label>
<select name="state">
{# You may also manually hardcode each option as well, as long as these options exist inside the form builder #}
{% for option in state.options %}
<option value="{{ option.value }}" {{ option.value in state.value ? "selected" }}>{{ option.label }}</option>
{% endfor %}
</select>

{# Manual submit buttons #}
{% set page = form.pages.current %}

<button {{ page.buttons.submitRenderProps({ 'data-optional': 'attributes here' }) }}>
Submit
</button>
{% if form.pages.current.buttons.back %}
<button {{ page.buttons.backRenderProps }}>
Back
</button>
{% endif %}
{% if form.pages.current.buttons.save %}
<button {{ page.buttons.saveRenderProps }}>
Save
</button>
{% endif %}

{{ form.renderClosingTag }}

Populate Freeform Field Data

If you'd like to populate a Freeform field with data from another element such as Craft Entries, you might introduce a conditional with code that looks something like this:

Freeform explicitly requires that all options exist for the field in the form builder work with field types like Dropdown, Checkboxes, etc. To work around this, you can create the field as a regular Text field instead, and then modify the formatting template to account for it and include your own Twig code to pull in dynamic options.

{% elseif field.handle == "myFieldHandle") %}

<select name="{{ field.handle }}">
{% for entry in craft.entries.section('news').limit(10) %}
<option value="{{ entry.handle }}"{% if field.value == entry.handle %} selected{% endif %}>
{{ entry.title }}
</option>
{% endfor %}
</select>

{% else %}