A layout wrapper for a form control, its label, description and validation feedback, built on CSS grid.
<CFormField> renders the .form-field wrapper, which gives a control the layout and spacing it needs alongside its label, description and validation feedback — so a form is spaced by the field itself rather than by margin utilities sprinkled over its parts.
<template>
<CFormField>
<CFormLabel for="fieldText">Email address</CFormLabel>
<CFormInput id="fieldText" type="email" placeholder="[email protected]" />
<CFormText>We'll never share your email with anyone else.</CFormText>
</CFormField>
</template>
<script setup>
import { CFormField, CFormInput, CFormLabel, CFormText } from '@coreui/vue'
</script>How it works
- Default layout is a single-column grid: everything stacks vertically with one gap. Use it for text inputs, selects, textareas, ranges, input groups and any of the PRO field components.
- Two-column layout switches on through
:has()when the input<CCheckbox>,<CRadio>or<CSwitch>renders (.check,.radio,.switch) is a direct child — the control pins to the first column, the label and description stack in the second. The deprecated.form-check-inputtriggers it too, so the markup the legacy<CFormCheck>and<CFormSwitch>render lays out the same way. - A field label (
<CFormLabel>, rendering.form-label) spans both columns and sits above the control row. - Grouped controls — nest a
<CFormField>per control inside an outer one to put several radios or checkboxes under a shared label with a shared description.
Examples
Select
<template>
<CFormField>
<CFormLabel for="fieldSelect">Country</CFormLabel>
<CFormSelect
id="fieldSelect"
:options="[
{ label: 'Choose one…' },
{ label: 'United States', value: '1' },
{ label: 'Canada', value: '2' },
{ label: 'United Kingdom', value: '3' },
]"
/>
</CFormField>
</template>
<script setup>
import { CFormField, CFormLabel, CFormSelect } from '@coreui/vue'
</script>Textarea
<template>
<CFormField>
<CFormLabel for="fieldTextarea">Message</CFormLabel>
<CFormTextarea id="fieldTextarea" :rows="3" />
<CFormText>Be sure to include the relevant details.</CFormText>
</CFormField>
</template>
<script setup>
import { CFormField, CFormLabel, CFormText, CFormTextarea } from '@coreui/vue'
</script>Checkbox
<template>
<CFormField>
<CCheckbox id="fieldCheck" label="Subscribe to the newsletter" />
<CFormText>We send one email a month, and never share your address.</CFormText>
</CFormField>
</template>
<script setup>
import { CCheckbox, CFormField, CFormText } from '@coreui/vue'
</script>Grouped
Nest one <CFormField> per control inside an outer one, and give the group its own .form-label.
<template>
<CFormField>
<span class="form-label">Delivery</span>
<CFormField>
<CRadio id="fieldRadio1" name="fieldRadio" label="Standard" checked />
<CFormText>Three to five working days.</CFormText>
</CFormField>
<CFormField>
<CRadio id="fieldRadio2" name="fieldRadio" label="Express" />
<CFormText>Next working day, where available.</CFormText>
</CFormField>
</CFormField>
</template>
<script setup>
import { CFormField, CFormText, CRadio } from '@coreui/vue'
</script>Switch
<template>
<CFormField>
<CSwitch id="fieldSwitch" label="Email notifications" />
<CFormText>Turn this off and we will only contact you about your account.</CFormText>
</CFormField>
</template>
<script setup>
import { CFormField, CFormText, CSwitch } from '@coreui/vue'
</script>Overhead label
When a check, radio, or switch needs a field title above it, add a .form-label. It sits on its own row above the control, while the control’s own label and description keep their places:
<template>
<CFormField>
<span class="form-label">Terms and conditions</span>
<CCheckbox id="fieldOverheadCheck" label="I agree to the terms" />
<CFormText>Please read them before agreeing.</CFormText>
</CFormField>
</template>
<script setup>
import { CCheckbox, CFormField, CFormText } from '@coreui/vue'
</script>Validation
Feedback elements are children of the field like any other, so they inherit its gap and land under the control. Set validation-state on the form component and pass the message through feedback-valid / feedback-invalid.
<template>
<CFormField>
<CFormLabel for="fieldValidation">Username</CFormLabel>
<CFormInput
id="fieldValidation"
type="text"
model-value="jan kowalski"
validation-state="invalid"
feedback-invalid="A username cannot contain spaces."
/>
</CFormField>
</template>
<script setup>
import { CFormField, CFormInput, CFormLabel } from '@coreui/vue'
</script>See validation for the full picture, including the server-side and browser-native flavours.