To style when raw Form elements alone are not sufficient.
<form action="" method="POST" enctype="multipart/form-data" class="c-form">
<h3 class="c-form__title">
(Optional) Title of Form
</h3>
<p class="c-form__desc">
(Optional) Description of the form.
</p>
<ul class="c-form__errors" style="display:none;">
<li>
Sample <strong>form</strong>
error. <a href="https://example.com" target="_blank">Link.</a>
</li>
</ul>
<div class="c-form__field has-required">
<label for="name">
Name
<span class="c-form__star" aria-label="(required)">*</span>
</label>
<input type="text" name="name" required="" placeholder="Your name" id="name" />
<ul class="c-form__errors" style="display:none;">
<li>
Sample <strong>field</strong>
error. <a href="https://example.com" target="_blank">Link.</a>
</li>
</ul>
<div class="c-form__help">
We use this to identify your submission.
</div>
</div>
<div class="c-form__field">
<label for="favorite-fruit">Favorite Fruit</label>
<select name="favorite-fruit" id="favorite-fruit">
<option value="" selected="">
Please select an option
</option>
<option value="Apple">Apple</option>
<option value="Banana">Banana</option>
<option value="Cherry">Cherry</option>
<option value="Durian">Durian</option>
<option value="Other">Other</option>
<option value="Whatever">Whatever</option>
</select>
<ul class="c-form__errors" style="display:none;">
<li>
Sample <strong>field</strong>
error. <a href="https://example.com" target="_blank">Link.</a>
</li>
</ul>
<div class="c-form__help">
Choose your favorite fruit.
</div>
</div>
<div class="c-form__field">
<label for="wake-up-time">Wake Up Time</label>
<input type="time" name="wake-up-time" id="wake-up-time" />
<ul class="c-form__errors" style="display:none;">
<li>
Sample <strong>field</strong>
error. <a href="https://example.com" target="_blank">Link.</a>
</li>
</ul>
<div class="c-form__help">
We use this to know when to wake you up.
</div>
</div>
<div class="c-form__field">
<label>Radio stations</label>
<menu id="radio-stations">
<li>
<label for="radio-stations-0">
<input type="radio" name="radio-stations" value="LX 1234" id="radio-stations-0" />
LX 1234
</label>
</li>
<li>
<label for="radio-stations-1">
<input type="radio" name="radio-stations" value="LOVE 45" id="radio-stations-1" />
LOVE 45
</label>
</li>
<li>
<label for="radio-stations-2">
<input type="radio" name="radio-stations" value="OLD 555" id="radio-stations-2" />
OLD 555
</label>
</li>
</menu>
<ul class="c-form__errors" style="display:none;">
<li>
Sample <strong>field</strong>
error. <a href="https://example.com" target="_blank">Link.</a>
</li>
</ul>
<div class="c-form__help">
Which radio stations do you prefer?
</div>
</div>
<div class="c-form__field has-type-check">
<input type="checkbox" name="checking-out" id="checking-out" />
<label for="checking-out">Checking Out?</label>
<ul class="c-form__errors" style="display:none;">
<li>
Sample <strong>field</strong>
error. <a href="https://example.com" target="_blank">Link.</a>
</li>
</ul>
<div class="c-form__help">
Are you checking out today?
</div>
</div>
<div class="c-form__field">
<label for="">
Which fish do you want?
</label>
<menu id="which-fish-do-you-want">
<li>
<label for="which-fish-do-you-want-0">
<input type="checkbox" name="which-fish-do-you-want" value="one fish" id="which-fish-do-you-want-0" />
one fish
</label>
</li>
<li>
<label for="which-fish-do-you-want-1">
<input type="checkbox" name="which-fish-do-you-want" value="two fish" id="which-fish-do-you-want-1" />
two fish
</label>
</li>
<li>
<label for="which-fish-do-you-want-2">
<input type="checkbox" name="which-fish-do-you-want" value="red fish" id="which-fish-do-you-want-2" />
red fish
</label>
</li>
<li>
<label for="which-fish-do-you-want-3">
<input type="checkbox" name="which-fish-do-you-want" value="blue fish" id="which-fish-do-you-want-3" />
blue fish
</label>
</li>
<li>
<label for="which-fish-do-you-want-4">
<input type="checkbox" name="which-fish-do-you-want" value="all the fish" id="which-fish-do-you-want-4" />
all the fish
</label>
</li>
</menu>
<ul class="c-form__errors" style="display:none;">
<li>
Sample <strong>field</strong>
error. <a href="https://example.com" target="_blank">Link.</a>
</li>
</ul>
</div>
<div class="c-form__field">
<label for="hangry-time">Hangry Time</label>
<select name="hangry-time" id="hangry-time">
<option value="" selected="">
Please select an option
</option>
<option value="Morning">Morning</option>
<option value="Noon">Noon</option>
<option value="Afternoon">Afternoon</option>
<option value="Evening">Evening</option>
<option value="Night">Night</option>
<option value="Midnight">Midnight</option>
</select>
<ul class="c-form__errors" style="display:none;">
<li>
Sample <strong>field</strong>
error. <a href="https://example.com" target="_blank">Link.</a>
</li>
</ul>
<div class="c-form__help">
At which time of day do you get hangry?
</div>
</div>
<div class="c-form__field">
<label for="life-story">Life Story</label>
<textarea name="life-story" cols="40" rows="10" id="life-story"></textarea>
<ul class="c-form__errors" style="display:none;">
<li>
Sample <strong>field</strong>
error. <a href="https://example.com" target="_blank">Link.</a>
</li>
</ul>
<div class="c-form__help">
Go on, tell me everything.
</div>
</div>
<div class="c-form__field">
<label for="what-day-is-it">What day is it?</label>
<input type="date" name="what-day-is-it" id="what-day-is-it" />
<ul class="c-form__errors" style="display:none;">
<li>
Sample <strong>field</strong>
error. <a href="https://example.com" target="_blank">Link.</a>
</li>
</ul>
<div class="c-form__help">
Please enter today's date.
</div>
</div>
<div class="c-form__field">
<label for="your-favorite-picture">Your favorite picture</label>
<input type="file" name="your-favorite-picture" id="your-favorite-picture" />
<ul class="c-form__errors" style="display:none;">
<li>
Sample <strong>field</strong>
error. <a href="https://example.com" target="_blank">Link.</a>
</li>
</ul>
<div class="c-form__help">
Upload your favorite image.
<small class="c-form__help">
Supported types:
<code>.jpeg</code>,<code>.jpg</code>,<code>.gif</code>,<code>.png</code>
</small>
</div>
</div>
<div class="c-form__field has-spam-check has-required">
<label for="recaptcha_g9y93gP8">
Are you a robot?
<span class="c-form__star" aria-label="(required)">*</span>
</label>
<script src="https://www.google.com/recaptcha/api.js"></script>
<script type="text/javascript">
// Submit function to be called, after reCAPTCHA was successful. var
onSubmit_9e32f28ccb9545109ff6ae891f9af324 = function(token) {
" "
} {
console.log("reCAPTCHA validated for 'data-widget-uuid=\"9e32f28ccb9545109ff6ae891f9af324\"'")
};
</script>
<div class="g-recaptcha" data-sitekey="6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI" required="" id="recaptcha_g9y93gP8" data-widget-uuid="9e32f28ccb9545109ff6ae891f9af324" data-callback="onSubmit_9e32f28ccb9545109ff6ae891f9af324" data-size="normal"></div>
</div>
<fieldset class="c-form__field has-spam-check has-required">
<legend>sample fields in a fieldset</legend>
<div class="c-form__field has-required">
<label for="name">
Name
<span class="c-form__star" aria-label="(required)">*</span>
</label>
<input type="text" name="name" required="" placeholder="Your name" id="name" />
<ul class="c-form__errors" style="display:none;">
<li>
Sample <strong>field</strong>
error. <a href="https://example.com" target="_blank">Link.</a>
</li>
</ul>
<div class="c-form__help">
We use this to identify your submission.
</div>
</div>
<div class="c-form__field">
<label for="favorite-fruit">Favorite Fruit</label>
<select name="favorite-fruit" id="favorite-fruit">
<option value="" selected="">
Please select an option
</option>
<option value="Apple">Apple</option>
<option value="Banana">Banana</option>
<option value="Cherry">Cherry</option>
<option value="Durian">Durian</option>
<option value="Other">Other</option>
<option value="Whatever">Whatever</option>
</select>
<ul class="c-form__errors" style="display:none;">
<li>
Sample <strong>field</strong>
error. <a href="https://example.com" target="_blank">Link.</a>
</li>
</ul>
<div class="c-form__help">
Choose your favorite fruit.
</div>
</div>
</fieldset>
<fieldset>
<legend>sample buttons outside footer</legend>
<button class="c-form__button" type="submit">
Submit
</button>
<button class="c-form__button" type="button">
Button
</button>
<button class="c-form__button" type="reset">
Reset
</button>
</fieldset>
<footer class="c-form__buttons">
<button class="c-form__button" type="submit">
Submit
</button>
<button id="toggle-field-errors" class="c-form__button" type="button">
Toggle Field Errors
</button>
<button id="toggle-required-fields" class="c-form__button" type="button">
Toggle Required Fields
</button>
<button class="c-form__button" type="reset">
Reset Field Values
</button>
</footer>
</form>
<script type="module">
import toggleFieldErrors from '../raw/c-form/toggle-field-errors.js';
document.addEventListener('DOMContentLoaded', () => {
const toggle = document.getElementById('toggle-field-errors');
const form = document.querySelector('form');
const selector = '.c-form__errors'
toggle.addEventListener('click', () => {
toggleFieldErrors(form, selector);
});
});
</script>
<script type="module">
import toggleRequiredFields from '../raw/c-form/toggle-required-fields.js';
document.addEventListener('DOMContentLoaded', () => {
const toggle = document.getElementById('toggle-required-fields');
const form = toggle.closest('form');
toggle.addEventListener('click', () => {
toggleRequiredFields(form, {
shouldScrollToFirst: true,
classNames: {
wrapRequired: 'has-required'
},
selectors: {
wrap: '.c-form__field'
}
});
});
});
</script>
<script type="module">
document.addEventListener('DOMContentLoaded', () => {
const form = document.querySelector('form');
form.addEventListener('submit', event => {
event.preventDefault();
/* TODO: Show success message on form */
alert('Form was not submitted, because this is an incomplete demo.');
});
});
</script>
/** To toggle error list visibility */
/**
* @constant
* @default
* @type {object.<string,*>}
*/
const DEFAULT_CLASS_NAMES = {
jsShow: 'js-show-errors',
jsHide: 'js-hide-errors'
};
/**
* @constant
* @default
* @type {object.<string,*>}
*/
const DEFAULT_CALLBACKS = {
hideEach: function ( list ) {
list.style.display = 'none';
},
showEach: function ( list ) {
list.style.display = '';
},
showAll: ( lists ) => {
lists[0].scrollIntoView( false );
}
};
/**
* On each error list hide/show
* @callback onEach
* @param {HTMLElement} list
*/
/**
* After all error lists are hidden/shown
* @callback afterAll
* @param {array.<HTMLElement>} lists
*/
/**
* HIDE an error list
* @param {HTMLInputElement|HTMLSelectElement|HTMLTextAreaElement} field
* @param {string} errorListClass - class on field's wrapper
*/
function hideErrorList( list, classNames, callback ) {
list.classList.add( classNames.jsHide );
list.classList.remove( classNames.jsShow );
if ( typeof callback === 'function' ) {
callback( list );
}
}
/**
* SHOW an error list
* @param {HTMLInputElement|HTMLSelectElement|HTMLTextAreaElement} field
* @param {string} errorListClass - class on field's wrapper
*/
function showErrorList( list, classNames, callback ) {
list.classList.add( classNames.jsShow );
list.classList.remove( classNames.jsHide );
if ( typeof callback === 'function' ) {
callback( list );
}
}
/**
* Toggle error lists' visibility
* @param {Document|HTMLElement} [scope=document] - where to search for lists
* @param {string} [selector] - selector to find error lists
* @param {object.<string,function>} [optCallbacks]
* @param {onEach} [optCallbacks.showEach] - on each list show
* @param {onEach} [optCallbacks.hideEach] - on each list hide
* @param {afterAll} [optCallbacks.showAll] - on all lists shown
* @param {afterAll} [optCallbacks.hideAll] - on all lists hidden
*/
export default function toggleErrorLists( scope, selector, optCallbacks ) {
const classNames = DEFAULT_CLASS_NAMES;
const callbacks = Object.assign( DEFAULT_CALLBACKS, optCallbacks );
const lists = scope.querySelectorAll( selector );
let didHide = false;
let didShow = false;
[ ...lists ].forEach( list => {
if ( list.classList.contains( classNames.jsShow ) ) {
hideErrorList( list, classNames, callbacks.hideEach );
didHide = true;
} else {
showErrorList( list, classNames, callbacks.showEach );
didShow = true;
}
});
if ( didHide && typeof callbacks.hideAll === 'function' ) {
callbacks.hideAll( lists );
}
if ( didShow && typeof callbacks.showAll === 'function' ) {
callbacks.showAll( lists );
}
}
/** To toggle required attribute (and classes) of fields (and field wrappers) */
/**
* @constant
* @default
* @type {object.<string,*>}
*/
const DEFAULT_CLASS_NAMES = {
/* The class added when required state has been changed and is active */
jsIs: 'js-is-required',
/* The class added when required state has been changed and is not active */
jsNot: 'js-not-required',
/* The class for the wrapper of a field that is required */
wrapRequired: undefined,
};
/**
* @constant
* @default
* @type {object.<*,*>}
*/
const DEFAULT_OPTIONS = {
/* @type {boolean} - should scroll to the first required field */
shouldScrollToFirst: false,
/* @type {DEFAULT_CLASS_NAMES} */
classNames: {}
};
/**
* @constant
* @default
* @type {object.<string,*>}
*/
const DEFAULT_SELECTORS = {
/* The wrapper of a field */
wrap: 'div'
};
/**
* Make a field NOT required (and update field wrapper accordingly)
* @param {HTMLInputElement|HTMLSelectElement|HTMLTextAreaElement} field
* @param {DEFAULT_CLASS_NAMES} classNames
* @param {DEFAULT_SELECTORS} selectors
*/
function unRequireField( field, classNames, selectors ) {
const wrap = field.closest( selectors.wrap );
field.required = false;
field.classList.add( classNames.jsNot );
field.classList.remove( classNames.jsIs );
if ( wrap && classNames.wrapRequired ) {
wrap.classList.remove( classNames.wrapRequired );
}
}
/**
* Make a field REQUIRED (and update field wrapper accordingly)
* @param {HTMLInputElement|HTMLSelectElement|HTMLTextAreaElement} field
* @param {DEFAULT_CLASS_NAMES} classNames
* @param {DEFAULT_SELECTORS} selectors
*/
function requireField( field, classNames, selectors ) {
const wrap = field.closest( selectors.wrap );
field.required = true;
field.classList.add( classNames.jsIs );
field.classList.remove( classNames.jsNot );
if ( wrap && classNames.wrapRequired ) {
wrap.classList.add( classNames.wrapRequired );
}
}
/**
* Toggle required attribute (and classes) of fields (and field wrappers)
* @param {Document|HTMLElement} [scope=document] - where to search for fields
* @param {object.<string,*>} [opts]
* @param {DEFAULT_CLASS_NAMES} [opts.classNames] - to scroll to first such field
* @param {DEFAULT_SELECTORS} [opts.selectors] - to scroll to first such field
* @param {boolean} [opts.shouldScrollToFirst] - to scroll to first such field
*/
export default function toggleRequiredFields( scope = document, opts ) {
const classNames = Object.assign( DEFAULT_CLASS_NAMES, opts.classNames );
const selectors = Object.assign( DEFAULT_SELECTORS, opts.selectors );
const options = Object.assign( DEFAULT_OPTIONS, opts );
const requiredFields = scope.querySelectorAll(`
[required]:is(input, select, textarea),
.` + classNames.jsNot + `
`);
[ ...requiredFields ].forEach( field => {
if ( field.hasAttribute('required') ) {
unRequireField( field, classNames, selectors );
} else {
requireField( field, classNames, selectors );
}
});
if ( options.shouldScrollToFirst ) {
requiredFields[0].scrollIntoView( false );
}
}
{
"shouldSkipPattern": true,
"globalStyles": [
{
"isInternal": true,
"layer": "base",
"path": "/assets/core-styles.demo.css"
},
{
"isInternal": true,
"layer": "base",
"path": "/assets/core-styles.base.css"
}
],
"cmsStyles": [
{
"isInternal": true,
"layer": "base",
"path": "/assets/core-styles.cms.css"
}
],
"docsStyles": [
{
"isInternal": true,
"layer": "base",
"path": "/assets/core-styles.docs.css"
}
],
"portalStyles": [
{
"isInternal": true,
"layer": "base",
"path": "/assets/core-styles.portal.css"
}
],
"subdir": "components",
"helpTextTag": "div",
"inputListTag": "menu",
"selectors": {
"errorList": ".c-form__errors",
"wrap": ".c-form__field"
},
"classNames": {
"root": "c-form",
"title": "c-form__title",
"desc": "c-form__desc",
"errors": "c-form__errors",
"field": "c-form__field",
"badge": "c-form__star",
"hint": "c-form__help",
"has_required": "has-required",
"has_checkbox": "has-type-check",
"has_spam_filter": "has-spam-check",
"buttons": "c-form__buttons",
"button": "c-form__button",
"nav": "c-form__nav"
},
"shouldLoadPortal": true
}