Field
componentA form field: its label, its control, its help and its errors, together; and sets of fields under a legend.
Preview
The address we write to.
This address is already taken.
Code
<twig:Field :invalid="true">
<twig:Field:Label for="email">Email address</twig:Field:Label>
<twig:Input id="email" type="email" name="email" aria-describedby="email-help email-error" aria-invalid="true" />
<twig:Field:Description id="email-help">The address we write to.</twig:Field:Description>
<twig:Field:Error id="email-error" :errors="['This address is already taken.']" />
</twig:Field>
The address we write to.
This address is already taken.
Code
<twig:Field :invalid="true">
<twig:Field:Label for="email">Email address</twig:Field:Label>
<twig:Input id="email" type="email" name="email" aria-describedby="email-help email-error" aria-invalid="true" />
<twig:Field:Description id="email-help">The address we write to.</twig:Field:Description>
<twig:Field:Error id="email-error" :errors="['This address is already taken.']" />
</twig:Field>
Try it
Change the props; the example is rendered again by the server.
The address we write to.
This address is already taken.
<twig:Field :invalid="true">
<twig:Field:Label for="email">Email address</twig:Field:Label>
<twig:Input id="email" type="email" name="email" aria-describedby="email-help email-error" aria-invalid="true" />
<twig:Field:Description id="email-help">The address we write to.</twig:Field:Description>
<twig:Field:Error id="email-error" :errors="['This address is already taken.']" />
</twig:Field>
<twig:Field :invalid="true">
<twig:Field:Label for="email">Email address</twig:Field:Label>
<twig:Input id="email" type="email" name="email" aria-describedby="email-help email-error" aria-invalid="true" />
<twig:Field:Description id="email-help">The address we write to.</twig:Field:Description>
<twig:Field:Error id="email-error" :errors="['This address is already taken.']" />
</twig:Field>
Variants
orientation
How the label and the control are laid out; responsive goes horizontal on a wide container.
The address we write to.
This address is already taken.
The address we write to.
This address is already taken.
The address we write to.
This address is already taken.
The address we write to.
This address is already taken.
The address we write to.
This address is already taken.
The address we write to.
This address is already taken.
invalid
Whether the field holds an invalid value: its error shows and its colours say so.
The address we write to.
This address is already taken.
The address we write to.
This address is already taken.
The address we write to.
This address is already taken.
The address we write to.
This address is already taken.
Installation
php bin/console ux:install field --kit=github.com/parler-haut-interagir-librement/ux-kit
Props
| Prop | Type | Default | Description |
|---|---|---|---|
orientation |
'vertical'|'horizontal'|'responsive' |
'vertical' |
How the label and the control are laid out; responsive goes horizontal on a wide container. |
invalid |
bool |
false |
Whether the field holds an invalid value: its error shows and its colours say so. |
Accessibility
The label names the control through for; the description and the error are tied to it by aria-describedby and aria-invalid, which the application writes on the control. The error is a live alert, written only when there is one. A Field:Set is a fieldset, its Field:Legend the name of the group.
Files to copy
templates/components/Field.html.twig
{%- props
## 'vertical'|'horizontal'|'responsive' How the label and the control are laid out; responsive goes horizontal on a wide container.
orientation = 'vertical',
## bool Whether the field holds an invalid value: its error shows and its colours say so.
invalid = false
-%}
{%- set _orientation = orientation in ['vertical', 'horizontal', 'responsive'] ? orientation : 'vertical' -%}
<div role="group" data-slot="field" data-orientation="{{ _orientation }}"{% if invalid %} data-invalid="true"{% endif %}{{ attributes.defaults({class: 'isField' ~ ({horizontal: ' isHorizontal', responsive: ' isResponsive'}[_orientation] ?? '') ~ (invalid ? ' isDanger' : '')}) }}>
{##- A `Field:Label`, the control, and optionally a `Field:Description` and a `Field:Error`. -#}
{%- block content %}{% endblock -%}
</div>
templates/components/Field/Content.html.twig
<div data-slot="field-content"{{ attributes.defaults({class: 'isFieldContent'}) }}>
{##- The label and the description of a horizontal field, kept together beside its control. -#}
{%- block content %}{% endblock -%}
</div>
templates/components/Field/Description.html.twig
<p data-slot="field-description"{{ attributes.defaults({class: 'isFieldDescription'}) }}>
{##- A help text, which the control names with `aria-describedby`. -#}
{%- block content %}{% endblock -%}
</p>
templates/components/Field/Error.html.twig
{#-
# The errors of a field: a list when there are several, a paragraph for one, nothing for none. With content in its
# block, the block is written instead.
#}
{%- props
## list<string> The error messages, each escaped.
errors = []
-%}
{%- set _content -%}
{##- An error written by hand, in place of `errors`. -#}
{%- block content %}{% endblock -%}
{%- endset -%}
{%- if _content|trim is not empty or errors is not empty -%}
<div role="alert" data-slot="field-error"{{ attributes.defaults({class: 'isFieldError'}) }}>
{%- if _content|trim is not empty -%}
{{ _content }}
{%- elseif errors|length == 1 -%}
<p>{{ errors|first }}</p>
{%- else -%}
<ul>{% for _error in errors %}<li>{{ _error }}</li>{% endfor %}</ul>
{%- endif -%}
</div>
{%- endif -%}
templates/components/Field/Group.html.twig
<div data-slot="field-group"{{ attributes.defaults({class: 'isFieldGroup'}) }}>
{##- Fields stacked together, each a `Field`. -#}
{%- block content %}{% endblock -%}
</div>
templates/components/Field/Label.html.twig
<label data-slot="field-label"{{ attributes.defaults({class: 'isFieldLabel'}) }}>
{##- The name of the control, which `for` points at, or the control itself inside the label. -#}
{%- block content %}{% endblock -%}
</label>
templates/components/Field/Legend.html.twig
{%- props
## 'legend'|'label' Whether the legend reads as a title or as the size of a label.
variant = 'legend'
-%}
<legend data-slot="field-legend" data-variant="{{ variant }}"{{ attributes.defaults({class: 'isFieldLegend' ~ (variant is same as('label') ? ' isSmall' : '')}) }}>
{##- The name of the set of fields. -#}
{%- block content %}{% endblock -%}
</legend>
templates/components/Field/Separator.html.twig
<div role="separator" data-slot="field-separator"{{ attributes.defaults({class: 'isFieldSeparator'}) }}>
{##- An optional text in the middle of the line, such as "or". -#}
{%- block content %}{% endblock -%}
</div>
templates/components/Field/Set.html.twig
<fieldset data-slot="field-set"{{ attributes.defaults({class: 'isFieldSet'}) }}>
{##- A `Field:Legend` and the fields of the set, often in a `Field:Group`. -#}
{%- block content %}{% endblock -%}
</fieldset>
templates/components/Field/Title.html.twig
<div data-slot="field-title"{{ attributes.defaults({class: 'isFieldTitle'}) }}>
{##- A title inside a field, for a control that has its own label. -#}
{%- block content %}{% endblock -%}
</div>