Form

Examples and usage guidelines for form control styles, layout options, and custom components for creating a wide variety of forms.

Overview#

CAKE’s form controls expand on our Rebooted form styles with classes. Use these classes to opt into their customized displays for a more consistent rendering across browsers and devices.

Be sure to use an appropriate type attribute on all inputs (e.g., email for email address or number for numerical information) to take advantage of newer input controls like email verification, number selection, and more.

Keep reading for documentation on required classes, form layout, and more.

Form controls#

Textual form controls—like <input>s, <select>s, and <textarea>s—are styled with the .form-control class. Included are styles for general appearance, focus state, sizing, and more.

Be sure to explore our custom form to further style <select>s.

Checkboxes and radios#

Checkboxes are for selecting one or several options in a list, while radios are for selecting one option from many.

Disabled checkboxes and radios are supported. The disabled attribute will apply a lighter color to help indicate the input's state.

Checkboxes and radios use are built to support HTML-based form validation and provide concise, accessible labels. As such, our <input>s and <label>s are sibling elements as opposed to an <input> within a <label>. This is slightly more verbose as you must specify id and for attributes to relate the <input> and <label>.

For even more customization and cross browser consistency, we use completely custom form elements to replace the browser defaults. They’re built on top of semantic and accessible markup, so they’re solid replacements for any default form control.

Each checkbox and radio <input> and <label> pairing is wrapped in a <div> to create our custom control. Structurally, this is the same approach as our default .form-check.

We use the sibling selector (~) for all our <input> states—like :checked—to properly style our custom form indicator. When combined with the .custom-control-label class, we can also style the text for each item based on the <input>’s state.

We hide the default <input> with opacity and use the .custom-control-label to build a new custom form indicator in its place with ::before and ::after. Unfortunately we can’t build a custom one from just the <input> because CSS’s content doesn’t work on that element.

In the checked states, we use base64 embedded SVG icons. This provides us the best control for styling and positioning across browsers and devices.

Radio#

Radio default#

preview

<div class="custom-control custom-radio ">
    <input type="radio" class="custom-control-input " id="43147d01" name="radio-default" />
    <label for="43147d01" class="custom-control-label">Check this custom radio</label>
</div>
<div class="custom-control custom-radio ">
    <input type="radio" class="custom-control-input " id="bff9940d" name="radio-default" />
    <label for="bff9940d" class="custom-control-label">Check this custom radio</label>
</div>
<div class="custom-control custom-radio ">
    <input type="radio" class="custom-control-input " id="077aa7ce" disabled name="radio-default" />
    <label for="077aa7ce" class="custom-control-label">Check this custom radio disabled</label>
</div>

Radio disabled and checked#

preview

<div class="custom-control custom-radio ">
    <input type="radio" class="custom-control-input " id="d59cbefe" disabled
        name="radio-disabled-checked" checked />
    <label for="d59cbefe" class="custom-control-label">Check this custom radio disabled
        checked</label>
</div>

Radio error#

preview

<div class="form-group">
    <div class="custom-control custom-radio ">
        <input type="radio" class="custom-control-input is-invalid" id="5a015584"
            name="radio-error" />
        <label for="5a015584" class="custom-control-label">Check this custom radio error</label>
        <div class="invalid-feedback">Example invalid feedback text</div>
    </div>
</div>

Radio required (validated)#

preview

<div class="form-group was-validated">
    <div class="custom-control custom-radio ">
        <input type="radio" class="custom-control-input " id="293bb94f"
            name="radio-required-validated" required />
        <label for="293bb94f" class="custom-control-label">Check this custom radio required
            validated</label>
        <div class="invalid-feedback">Example invalid feedback text</div>
    </div>
</div>

Checkbox#

Checkbox default#

preview

<div class="custom-control custom-checkbox ">
    <input type="checkbox" class="custom-control-input " id="6130e1a4" />
    <label for="6130e1a4" class="custom-control-label">Check this custom checkbox</label>
</div>
<div class="custom-control custom-checkbox ">
    <input type="checkbox" class="custom-control-input " id="f6ff2779" />
    <label for="f6ff2779" class="custom-control-label">Check this custom checkbox</label>
</div>
<div class="custom-control custom-checkbox ">
    <input type="checkbox" class="custom-control-input " id="c829b253" disabled />
    <label for="c829b253" class="custom-control-label">Check this custom checkbox disabled</label>
</div>

Checkbox disabled and checked#

preview

<div class="custom-control custom-checkbox ">
    <input type="checkbox" class="custom-control-input " id="c8f18484" disabled checked />
    <label for="c8f18484" class="custom-control-label">Check this custom checkbox disabled
        checked</label>
</div>

Checkbox error#

preview

<div class="form-group">
    <div class="custom-control custom-checkbox ">
        <input type="checkbox" class="custom-control-input is-invalid" id="8895b9aa" />
        <label for="8895b9aa" class="custom-control-label">Check this custom checkbox error</label>
        <div class="invalid-feedback">Example invalid feedback text</div>
    </div>
</div>

Checkbox required (validated)#

preview

<div class="form-group was-validated">
    <div class="custom-control custom-checkbox ">
        <input type="checkbox" class="custom-control-input " id="cbd0745a" required />
        <label for="cbd0745a" class="custom-control-label">Check this custom checkbox required
            validated</label>
        <div class="invalid-feedback">Example invalid feedback text</div>
    </div>
</div>

Default (stacked)#

By default, any number of checkboxes and radios that are immediate sibling will be vertically stacked and appropriately spaced with .custom-control.

<div class="custom-control custom-checkbox">
    <input class="custom-control-input" type="checkbox" value="" id="CocRz3dvysE">
    <label class="custom-control-label" for="CocRz3dvysE">
        Check this custom checkbox
    </label>
</div>
<div class="custom-control custom-checkbox">
    <input class="custom-control-input" type="checkbox" value="" id="zzIrIlYr64R" disabled>
    <label class="custom-control-label" for="zzIrIlYr64R">
        Check this custom checkbox disabled
    </label>
</div>
<div class="custom-control custom-radio">
    <input class="custom-control-input" type="radio" id="W81HFtR5Qj5" name="radio-default">
    <label class="custom-control-label" for="W81HFtR5Qj5">
        Custom default radio
    </label>
</div>
<div class="custom-control custom-radio">
    <input class="custom-control-input" type="radio" id="l9Rg-__4SMF" name="radio-default">
    <label class="custom-control-label" for="l9Rg-__4SMF">
        Second custom default radio
    </label>
</div>
<div class="custom-control custom-radio">
    <input class="custom-control-input" type="radio" id="caz_fjEhjzl" disabled name="radio-default">
    <label class="custom-control-label" for="caz_fjEhjzl">
        Disabled custom radio
    </label>
</div>

Inline#

<div class="custom-control custom-radio custom-control-inline">
    <input type="radio" id="customRadioInline1" name="customRadioInline1" class="custom-control-input">
    <label class="custom-control-label" for="customRadioInline1">
        Toggle this custom radio
    </label>
</div>
<div class="custom-control custom-radio custom-control-inline">
    <input type="radio" id="customRadioInline2" name="customRadioInline1" class="custom-control-input">
    <label class="custom-control-label" for="customRadioInline2">
        Or toggle this other custom radio
    </label>
</div>

Select menu#

Custom <select> menus need only a custom class, .custom-select to trigger the custom styles. Custom styles are limited to the <select>’s initial appearance and cannot modify the <option>s due to browser limitations.

<label for="ZiiLYTFVw" class="select-label">Label for select</label>
<select class="custom-select" id="ZiiLYTFVw">
    <option>Open this select menu</option>
    <option value="1">One</option>
    <option value="2">Two</option>
    <option value="3">Three</option>
</select>

Input#

Text#

preview

<div>
    <label for="6fc37b77" class="input-label ">Label for Input</label>
    <div class="input-group">
        <input type="text" class="form-control " id="6fc37b77" placeholder="Placeholder" />
    </div>
</div>

Label with sub text#

preview

<div>
    <label for="9115733f" class="input-label ">Label for Input<span class="font-weight-normal">
            (optional)</span>
    </label>
    <div class="input-group">
        <input type="text" class="form-control " id="9115733f" value="Default" />
    </div>
</div>

Disabled#

preview

<div>
    <label for="b64efb68" class="input-label ">Label for Input</label>
    <div class="input-group">
        <input type="text" class="form-control " id="b64efb68" disabled value="Disabled" />
    </div>
</div>

Invalid#

preview

<div>
    <label for="aeefbba2" class="input-label ">Label for Input</label>
    <div class="input-group">
        <input type="text" class="form-control is-invalid" id="aeefbba2" value="Error" />
    </div>
</div>

Valid#

preview

<div>
    <label for="35edb4a4" class="input-label ">Label for Input</label>
    <div class="input-group">
        <input type="text" class="form-control is-valid" id="35edb4a4" value="Success" />
    </div>
</div>

Text area#

Default#

preview

<div>
    <label for="4c45983a" class="input-label ">Label for Text area</label>
    <div class="input-group">
        <textarea class="form-control " id="4c45983a" type="text" placeholder="Placeholder">
</textarea>
    </div>
</div>

Label with sub text#

preview

<div>
    <label for="dd353974" class="input-label ">Label for Text area<span class="font-weight-normal">
            (optional)</span>
    </label>
    <div class="input-group">
        <textarea class="form-control " id="dd353974" type="text">Default</textarea>
    </div>
</div>

Disabled#

preview

<div>
    <label for="30bee2dc" class="input-label ">Label for Text area</label>
    <div class="input-group">
        <textarea class="form-control " id="30bee2dc" type="text" disabled>Disabled</textarea>
    </div>
</div>

Invalid#

preview

<div>
    <label for="ca300d35" class="input-label ">Label for Text area</label>
    <div class="input-group">
        <textarea class="form-control is-invalid" id="ca300d35" type="text">Error</textarea>
    </div>
</div>

Resizing#

By default, our textareas are not resizable by the user. However, to allow the user to change the size for project-specific requirements, we offer three modifier classes: .resizable, .resizable-horizontal and .resizable-vertical. You can see how these talking classes work in the following example.

preview

<div>
    <label for="66c4da2d" class="input-label ">Text area without resizing</label>
    <div class="input-group">
        <textarea class="form-control mb-2" id="66c4da2d" type="text">
</textarea>
    </div>
</div>
<div>
    <label for="018c9337" class="input-label ">Text area with horizontal and vertical
        resizing</label>
    <div class="input-group">
        <textarea class="form-control resizable mb-2" id="018c9337" type="text">
</textarea>
    </div>
</div>
<div>
    <label for="bdeb9fb7" class="input-label ">Text area with vertical resizing</label>
    <div class="input-group">
        <textarea class="form-control resizable-vertical mb-2" id="bdeb9fb7" type="text">
</textarea>
    </div>
</div>
<div>
    <label for="590e611c" class="input-label ">Text area with horizontal resizing</label>
    <div class="input-group">
        <textarea class="form-control resizable-horizontal" id="590e611c" type="text">
</textarea>
    </div>
</div>

Select#

Select default#

preview

<div>
    <label for="2e7de44f" class="select-label ">Label for Select</label>
    <select class="custom-select " id="2e7de44f">
        <option>Open this select menu</option>
        <option>One</option>
        <option>Two</option>
        <option>Three</option>
    </select>
</div>

Select screen reader only label#

preview

<div>
    <label for="3fd1340c" class="select-label sr-only">Label for Select</label>
    <select class="custom-select " id="3fd1340c">
        <option>first option</option>
        <option>second option</option>
        <option>third option</option>
    </select>
</div>

Select with hidden option#

preview

<div>
    <label for="85792dc4" class="select-label ">Label for Select</label>
    <select class="custom-select " id="85792dc4">
        <option hidden>first and hidden option in list</option>
        <option>second option</option>
        <option>third option</option>
    </select>
</div>

Select with disabled option#

preview

<div>
    <label for="b9c1f74a" class="select-label ">Label for Select</label>
    <select class="custom-select " id="b9c1f74a">
        <option>first option</option>
        <option disabled>second and disabled option</option>
        <option>third option</option>
    </select>
</div>

Select with preselected option#

preview

<div>
    <label for="501968ad" class="select-label ">Label for Select</label>
    <select class="custom-select " id="501968ad">
        <option>first option</option>
        <option selected>second and selected option</option>
        <option>third option</option>
    </select>
</div>

Select with label sub text#

preview

<div>
    <label for="dbaec485" class="select-label ">Label for Select<span class="font-weight-normal">
            (optional)</span>
    </label>
    <select class="custom-select " id="dbaec485">
        <option>first option</option>
        <option>second option</option>
    </select>
</div>

Select disabled#

preview

<div>
    <label for="9bb9cb1f" class="select-label ">Label for Select</label>
    <select class="custom-select " disabled id="9bb9cb1f">
        <option>first option</option>
        <option>second option</option>
    </select>
</div>

Select error#

preview

<div>
    <label for="ca20160e" class="select-label ">Label for Select</label>
    <select class="custom-select is-invalid" id="ca20160e">
        <option>first option</option>
        <option>second option</option>
    </select>
</div>

Select success#

preview

<div>
    <label for="09883890" class="select-label ">Label for Select</label>
    <select class="custom-select is-valid" id="09883890">
        <option>first option</option>
        <option>second option</option>
    </select>
</div>

Disabled forms#

Add the disabled boolean attribute to the <input> and the custom indicator and label description will be automatically styled.

Validation#

Provide valuable, actionable feedback to your users with HTML5 form validation–available in all our supported browsers. Choose from the browser default validation feedback, or implement custom messages with our built-in classes.

We currently recommend using custom validation styles, as native browser default validation messages are not consistently exposed to assistive technologies in all browsers (most notably, Chrome on desktop and mobile).

How it works#

Here’s how form validation works with CAKE:

  • HTML form validation is applied via CSS’s two pseudo-classes, :invalid and :valid. It applies to <input>, <select>, and <textarea> elements.
  • CAKE scopes the :invalid and :valid styles to parent .was-validated class, usually applied to the <form>. Otherwise, any required field without a value shows up as invalid on page load. This way, you may choose when to activate them (typically after form submission is attempted).
  • To reset the appearance of the form (for instance, in the case of dynamic form submissions using AJAX), remove the .was-validated class from the <form> again after submission.
  • As a fallback, .is-invalid and .is-valid classes may be used instead of the pseudo-classes for server side validation. They do not require a .was-validated parent class.
  • Due to constraints in how CSS works, we cannot (at present) apply styles to a <label> that comes before a form control in the DOM without the help of custom JavaScript.
  • All modern browsers support the constraint validation API, a series of JavaScript methods for validating form controls.
  • Feedback messages may utilize the browser defaults (different for each browser, and unstylable via CSS) or our custom feedback styles with additional HTML and CSS.
  • You may provide custom validity messages with setCustomValidity in JavaScript.
  • With that in mind, consider the following demos for our custom form validation styles, optional server side classes, and browser defaults.

Custom styles#

For custom CAKE form validation messages, you’ll need to add the novalidate boolean attribute to your <form>. This disables the browser default feedback tooltips, but still provides access to the form validation APIs in JavaScript. Try to submit the form below; our JavaScript will intercept the submit button and relay feedback to you. When attempting to submit, you'll see the :invalid and :valid styles applied to your form controls.

Custom feedback styles apply custom colors, borders, focus styles, and background icons to better communicate feedback. Background icons for <select>s are only available with .custom-select, and not .form-control.

Server side#

We recommend using client-side validation, but in case you require server-side validation, you can indicate invalid and valid form fields with .is-invalid and .is-valid. Note that .invalid-feedback is also supported with these classes.

Input group#

Easily extend form controls by adding text, buttons, or button groups on either side of textual inputs, custom selects, and custom file inputs.

Input with icon inside#

Inputs are wrapped inside a .input-group class, which enables possibility to add further content inside the displayed input.

<label class="input-label" for="IwPS1xcy1hq">Label for Input</label>
<div class="input-group">
    <input class="form-control" type="text" id="IwPS1xcy1hq" placeholder="Input!"/>
</div>

Example with icon#

This can be used to, for example wrap icons inside an input field.

Input with icon#

preview

<div>
    <label for="87c8a073" class="input-label ">Label for Input</label>
    <div class="input-group">
        <input type="text" class="form-control input-group-input-icon" id="87c8a073" />
        <span class="input-group-inside inputfield-icon">
            <svg class="icon" xmlns="http://www.w3.org/2000/svg" role="img" focusable="false">
                <title>Placeholder</title>
                <use xlink:href="../../.././_assets/images/icon__sprite.svg#placeholder">
                </use>
            </svg>
        </span>
    </div>
</div>

Input disabled with icon#

preview

<div>
    <label for="0eae80ec" class="input-label ">Label for Input</label>
    <div class="input-group">
        <input type="text" class="form-control input-group-input-icon" id="0eae80ec" disabled
            value="Disabled" />
        <span class="input-group-inside inputfield-icon">
            <svg class="icon" xmlns="http://www.w3.org/2000/svg" role="img" focusable="false">
                <title>Placeholder</title>
                <use xlink:href="../../.././_assets/images/icon__sprite.svg#placeholder">
                </use>
            </svg>
        </span>
    </div>
</div>

Customizing#

Validation states can be customized via Sass with the $form-validation-states map. Located in our _variables.scss file, this Sass map is looped over to generate the default valid/invalid validation states. Included is a nested map for customizing each state's color and icon. While no other states are supported by browsers, those using custom styles can easily add more complex form feedback.

Please note that we do not recommend customizing these values without also modifying the form-validation-state mixin.

// Sass map from `_variables.scss`
// Override this and recompile your Sass to generate different states
$form-validation-states: map-merge(
  (
    "valid": (
      "color": $form-feedback-valid-color,
      "icon": $form-feedback-icon-valid
    ),
    "invalid": (
      "color": $form-feedback-invalid-color,
      "icon": $form-feedback-icon-invalid
    )
  ),
  $form-validation-states
);

// Loop from `_forms.scss`
// Any modifications to the above Sass map will be reflected in your compiled
// CSS via this loop.
@each $state, $data in $form-validation-states {
  @include form-validation-state($state, map-get($data, color), map-get($data, icon));
}

Password toggle#

Additionaly to the standard inputs we provide a special field for password inputs. This password inputfield will show an additional button to toggle the input form type="password" to type="text" and back. To toggle between these two types use the icon on the right side that is wrapped inside a button. To do so, you have to add a button with the classes .inputfield-icon and .input-group-inside applied to it.

Javascript is required (js/form.js) to be able to use the toggle functionality. This functionality changes the input type from password to text. When using our JavaScript plugin form.js you have to add the data-controller="inputfield/password" to the input element and the toggle-button with data-controller="inputfield/password/toggle" as attribute. Our JavaScript will automatically add the toggle functionality to this input group. If the toggle-button should be hidden, when the user has JavaScript disabled, you can simply add the class .d-none to the toggle-button. If now JavaScript is enabled on the users device, the .d-none class will be automatically removed and therefore the toggle-button will be shown.

preview

<div>
    <label for="bfb72eb8" class="input-label ">Label for Input</label>
    <div class="input-group">
        <input type="password" class="form-control input-group-input-icon" id="bfb72eb8"
            data-controller="inputfield/password" />
        <button type="button" class="btn btn btn-link p-0 inputfield-icon input-group-inside d-none"
            data-controller="inputfield/password/toggle" aria-pressed="false">
            <svg class="icon inputfield-icon-password-invisible" xmlns="http://www.w3.org/2000/svg"
                role="img" focusable="false">
                <title>Show password</title>
                <use xlink:href="../../.././_assets/images/icon__sprite.svg#eye-close">
                </use>
            </svg>
            <svg class="icon inputfield-icon-password-visible" xmlns="http://www.w3.org/2000/svg"
                role="img" focusable="false">
                <title>Hide password</title>
                <use xlink:href="../../.././_assets/images/icon__sprite.svg#eye-open">
                </use>
            </svg>
        </button>
    </div>
</div>
import "./cakeDOM";

if (typeof window.cake !== "object") {
    window.cake = {};
}

const defaultOptions = {
    passwordInputElements: [],
    passwordInputQuerySelector: '[data-controller="inputfield/password"]',
    passwordToggleElements: [],
    passwordToggleQuerySelector: '[data-controller="inputfield/password/toggle"]'
};

window.cake.form = (options = defaultOptions) => {
    options = {
        ...defaultOptions,
        ...options
    };
    const passwordFields = window.cake.utils.getElements(options.passwordInputElements, options
        .passwordInputQuerySelector);

    passwordFields.forEach((passwordField, idx) => {
        const passwordToggle = window.cake.utils.getElement(options
            .passwordToggleElements[idx], options.passwordToggleQuerySelector,
            passwordField, "siblingSelector");

        if (passwordToggle) {
            passwordToggle.classList.remove('d-none');
            passwordToggle.onclick = function(ev) {
                ev.preventDefault();
                if (passwordField.getAttribute('type') === 'password') {
                    passwordField.setAttribute('type', "text");
                    passwordToggle.setAttribute('aria-pressed', "true");
                } else {
                    passwordField.setAttribute('type', "password");
                    passwordToggle.setAttribute('aria-pressed', "false");
                }
            }
        }
    });
};

export default window.cake.form;

Javascript#

The password toggle does only work with javascript. If javascript is disabled, the password field works as usual but without toggle functionality. You can import our javascript bundle cake.js to automatically use this feature. You only must ensure that the data-controllers explained above are set up.

Initialization#

To initialize the javascript with default configuration you could simply run the following code:

document.addEventListener ('DOMContentLoaded', () => {
    window.cake.form ();
});
Customization#

To customize the default behavior you can only embedd the form.js file into your mockups. Then you can initialize the functionality by calling:

document.addEventListener ('DOMContentLoaded', () => {
    window.cake.form (options = {
        passwordInputElements: [],
        passwordInputQuerySelector: '[data-controller="inputfield/password"]',
        passwordToggleElements: [],
        passwordToggleQuerySelector: '[data-controller="inputfield/password/toggle"]'
    });
});
  • passwordInputElements [Array] – provide the specific input[type=password] element with password toggle (optional)
  • passwordInputQuerySelector [String] – provide a query-selector to select all input[type=password] with password toggle (optional, default: [data-controller="inputfield/password"])
  • passwordToggleElements [Array] – provide the specific password toggle button (optional)
  • passwordToggleQuerySelector [String] – provide a query-selector to select all password toggle buttons (optional, default: [data-controller="inputfield/password/toggle"])

If you do provide the options.[...]Elements the options.[...]QuerySelector option gets ignored. If you do not provide any options.[...]Elements always the options.[...]QuerySelector is used!

Change log#

6.7.1 - 2022-08-08#

Fixed#

  • SCSS, Doc: "Form" | fixed wrong horizontal paddings of textarea elements

6.7.0 - 2022-07-25#

Added#

  • SCSS, Doc: "Form" | added stylings and documentation for textarea elements

Changed#

  • Doc: "Form" | restructured the content of form documentation to improve readability

6.4.2 - 2022-01-24#

Fixed#

  • SCSS: "Form" | fixed on focus invisibility in chrome on inputs with icon

6.2.0 - 2021-08-19#

Added#

  • Doc: "Form" | added javaScript file content as "JS" tab

5.0.0 - 2021-01-28#

Changed#

  • JS, Doc: "Form" | updated javascript to provide options object for better integration of CAKE
  • SCSS: "Form" | rename mixin form-validation-state to cake-form-validation-state
  • SCSS: "Form" | rename mixin dynamic-padding-y-for-fixed-height to cake-dynamic-padding-y-for-fixed-height

Fixed#

  • SCSS: "Form" | fixed correct calculation of the right pudding from inputs with icons

4.0.0 - 2020-06-25#

Fixed#

  • CSS: "Form" | Set font color for focused select field in Firefox instead of inheritance. The ESR 68 Version has a different behavior as newer once.

3.10.0 - 2020-02-27#

Deprecated#

  • CSS: "Form" | .text-disabled class becomes obsolete
  • SCSS: "Form" | $text-disabled variable becomes obsolete

Removed#

  • HTML: "Form" | Disabled form element labels no longer have a text-disabled modifier CSS class.

3.9.0 - 2020-01-16#

Changed#

  • SCSS: "Form" | added new height via calculated padding-y on xs-md for inputs and selects

3.8.0 - 2019-11-07#

Changed#

  • Doc: "Form" | Moved input-group documentation from menu "Input group" to "Form" (and deleted dedicated page "Input group")
  • SCSS: "Form" | Set !default to variable $custom-control-indicator-checked-danger-color