鳳 ‧ 極意?!
Paul Li
鳳 ‧ 極意?!
Paul Li
Paul Li
Agenda
Pick suitable elements
The input element
There are som many kinds <input /> for different purpose. Such as email、url、color、file、datetime、search、text、number、range...etc. This almost cover all kinds scenarios. Web developers could apply these input elements to build a vivid form UI/UX.
Use case 1.
Take <input /> UI / UX and methods to indicate various adjustments.
Use case 2.
With input[type=range] supported, Developers could save 「drag」、「drop」 event usage and don't need to detect current device.
Style your form
CSS > accent-color
The accent-color CSS property sets the accent color for user-interface controls generated by some elements.
<style> :nth-child(2 of .row) input { accent-color: #eb0f29; } :nth-child(3 of .row) input { accent-color: rgba(26 197 103); } </style>
Customize input element
With modern CSS supports, web developers could style
<div class="input-set"> <input name="input-name" type="text" placeholder="placeholder" /> <label class="input-set__label"> <span class="input-set__label__span">label</span> </label> <em class="input-set__em"></em> </div> <style> .input-set { --background-color: var(--default-background-color, rgba(var(--white))); --text-color: var(--default-text-color, rgba(var(--batcave))); --border-color: var(--default-border-color, rgba(var(--dolphin))); --err-color: var(--default-err-color, rgba(var(--solo-cup))); --err-text-color: var(--default-err-text-color, rgba(var(--dolphin))); --label-text-color: var(--default-label-text-color, rgba(var(--dolphin))); --theme: var(--default-theme, rgba(var(--dory))); --hover: var(--theme); /* readyonly, disabled */ --background-color-disabled: var(--default-background-color-disabled, rgba(var(--grey-hair))); --text-color-disabled: var(--default-text-color-disabled, rgba(var(--bob))); --border-color-disabled: var(--default-border-color-disabled, rgba(var(--bob))); --placeholder-text-color-normal: transparent; --placeholder-text-color-active: var(--default-placeholder-text-color, rgba(var(--charcoal))); --placeholder-text-color: var(--placeholder-text-color-normal); ... ... } .input-set > input::-webkit-input-placeholder { color: var(--placeholder-text-color); transition: color 100ms ease; will-change: color; } .input-set > input::-moz-placeholder { color: var(--placeholder-text-color); transition: color 100ms ease; will-change: color; } .input-set { position: relative; display: block; } .input-set > input { appearance: none; ... ... } .input-set > input:focus { outline: 0 none; } .input-set__label { ... } .input-set__label__span { ... } .input-set__label::after { ... } .input-set__em { ... } .input-set__em::before { ... } /* focus */ .input-set > input:focus { --border-color: var(--hover); --border-size: var(--border-size-active); } .input-set > input:focus::-webkit-input-placeholder { --placeholder-text-color: var(--placeholder-text-color-active); } .input-set > input:focus::-moz-placeholder { --placeholder-text-color: var(--placeholder-text-color-active); } .input-set > input:focus ~ .input-set__label { color: var(--hover); transform: translateY(-24px) scale(0.85); } .input-set > input:not(:placeholder-shown) ~ .input-set__label { transform: translateY(-24px) scale(0.85); } /* invalid */ .input-set > input:invalid ~ .input-set__em, .input-set > input[invalid] ~ .input-set__em { --err-display: flex; } .input-set > input:invalid, .input-set > input[invalid] { --border-color: var(--err-color); } /* inert, readonly, disabled */ [inert] .input-set, .input-set:has(input[readonly]), .input-set:has(input[disabled]) { --default-text-color: var(--text-color-disabled); --default-border-color: var(--border-color-disabled); --default-background-color: var(--background-color-disabled); } @media (hover: hover) { .input-set:hover { --border-color: var(--hover); } .input-set:hover > input:not(:placeholder-shown) ~ .input-set__label { --label-text-color: var(--hover); } } @media (prefers-color-scheme: dark) { .input-set { ... ... } } </style><input />
easily ever.
Customize select element
With modern CSS supports, web developers could style
<div class="input-set input-set--select"> <select name="select-name"> <optgroup label="Group A"> <option value="1">option 1</option> <option value="2">option 2</option> <option value="3">option 3</option> </optgroup> <optgroup label="Group B"> <option value="4">option 4</option> <option value="5">option 5</option> <option value="6">option 6</option> </optgroup> </select> <labelclass="input-set__label"> <span class="input-set__label__span">label</span> </label> <em class="input-set__em"></em> </div> <style> .input-set { --background-color: var(--default-background-color, rgba(var(--white))); --text-color: var(--default-text-color, rgba(var(--batcave))); --border-color: var(--default-border-color, rgba(var(--dolphin))); --err-color: var(--default-err-color, rgba(var(--solo-cup))); --err-text-color: var(--default-err-text-color, rgba(var(--dolphin))); --label-text-color: var(--default-label-text-color, rgba(var(--dolphin))); --theme: var(--default-theme, rgba(var(--dory))); --hover: var(--theme); /* readyonly, disabled */ --background-color-disabled: var(--default-background-color-disabled, rgba(var(--grey-hair))); --text-color-disabled: var(--default-text-color-disabled, rgba(var(--bob))); --border-color-disabled: var(--default-border-color-disabled, rgba(var(--bob))); --placeholder-text-color-normal: transparent; --placeholder-text-color-active: var(--default-placeholder-text-color, rgba(var(--charcoal))); --placeholder-text-color: var(--placeholder-text-color-normal); ... ... } /* select */ .input-set--select select { padding-inline-end: calc(var(--padding-inline-end) * 2 + 24px); } .input-set--select::after { ... } .input-set > select::-webkit-input-placeholder { color: var(--placeholder-text-color); transition: color 100ms ease; will-change: color; } .input-set > select::-moz-placeholder { color: var(--placeholder-text-color); transition: color 100ms ease; will-change: color; } .input-set { position: relative; display: block; } .input-set > select { appearance: none; ... ... } .input-set > select:focus { outline: 0 none; } .input-set__label { ... } .input-set__label__span { ... } .input-set__label::after { ... } .input-set__em { ... } .input-set__em::before { ... } /* focus */ .input-set > select:focus { --border-color: var(--hover); --border-size: var(--border-size-active); } .input-set > select:focus::-webkit-input-placeholder { --placeholder-text-color: var(--placeholder-text-color-active); } .input-set > select:focus::-moz-placeholder { --placeholder-text-color: var(--placeholder-text-color-active); } .input-set > select:focus ~ .input-set__label { color: var(--hover); transform: translateY(-24px) scale(0.85); } .input-set > select:not(:placeholder-shown) ~ .input-set__label { transform: translateY(-24px) scale(0.85); } /* invalid */ .input-set > select:invalid ~ .input-set__em, .input-set > select[invalid] ~ .input-set__em { --err-display: flex; } .input-set > select:invalid, .input-set > select[invalid] { --border-color: var(--err-color); } /* inert, readonly, disabled */ [inert] .input-set, .input-set:has(select[readonly]), .input-set:has(select[disabled]) { --default-text-color: var(--text-color-disabled); --default-border-color: var(--border-color-disabled); --default-background-color: var(--background-color-disabled); } @media (hover: hover) { .input-set:hover { --border-color: var(--hover); } .input-set:hover > select:not(:placeholder-shown) ~ .input-set__label { --label-text-color: var(--hover); } } @media (prefers-color-scheme: dark) { .input-set { ... ... } } </style><select />
easily ever.
Customize checkbox element
With modern CSS supports, web developers could style input[type=checkbox] easily ever.
<div class="checkbox-set"> <input id="my-checkbox" type="checkbox" name="checkbox[]" value="0" /> <label class="checkbox-set__label" for="my-checkbox"></label> </div> <style> .checkbox-set { /* normal */ --accent-color: var(--default-accent-color, rgba(42 108 246)); --border-color: var(--default-border-color, rgba(131 138 146)); --background-color: var(--default-background-color, rgba(255 255 255)); --checkmark-color: var(--default-checkmark-color, rgba(255 255 255)); /* disabled */ --border-color--disabled: var(--default-border-color--disabled, rgba(178 185 192)); --background-color--disabled: var(--default-background-color--disabled, rgba(255 255 255)); --checkmark-color--disabled: var(--default-checkmark-color--disabled, rgba(178 185 192)); ... ... } .checkbox-set__label { --checkmark-opacity-normal: 0; --checkmark-opacity-active: 1; --checkmark-opacity: var(--checkmark-opacity-normal); } .checkbox-set input:checked ~ .checkbox-set__label { --border-color: var(--accent-color); --background-color: var(--accent-color); --checkmark-opacity: var(--checkmark-opacity-active); } .checkbox-set input:not(:disabled):focus-visible ~ .checkbox-set__label { --border-color: var(--accent-color); } [inert] .checkbox-set input ~ .checkbox-set__label, .checkbox-set input:disabled ~ .checkbox-set__label { --border-color: var(--border-color--disabled); --background-color: var(--background-color--disabled); --checkmark-color: var(--checkmark-color--disabled); } .checkbox-set { position: relative; inline-size: var(--size); aspect-ratio: 1/1; overflow: hidden; } .checkbox-set input { position: absolute; top: -100%; outline: 0 none; } .checkbox-set__label { ... } .checkbox-set__label::before { ... } .checkbox-set__label::after { ... } @media (hover: hover) { .checkbox-set input:not(:disabled) ~ .checkbox-set__label:hover { --border-color: var(--accent-color); } } @media (prefers-color-scheme: dark) { .checkbox-set { ... ... } } </style>
Customize radio buttons element
With modern CSS supports, web developers could style input[type=radio] easily ever.
<div class="radio-set"> <input id="my-radio" type="radio" name="radio" value="0" /> <label class="radio-set__label" for="my-radio"></label> </div> <style> .radio-set { /* normal */ --accent-color: var(--default-accent-color, rgba(42 108 246)); --border-color: var(--default-border-color, rgba(131 138 146)); --background-color: var(--default-background-color, rgba(255 255 255)); --default-dot-color: var(--default-dot-color, rgba(accent-color)); /* disabled */ --border-color--disabled: var(--default-border-color--disabled, rgba(178 185 192)); --background-color--disabled: var(--default-background-color--disabled, rgba(255 255 255)); --default-dot-color--disabled: var(--default-accent-color, rgba(178 185 192)); ... ... } .radio-set__label { --dot-scale-normal: .001; --dot-scale-active: 1; --dot-scale: var(--dot-scale-normal); } .radio-set input:checked ~ .radio-set__label { --border-color: var(--accent-color); --dot-scale: var(--dot-scale-active); } .radio-set input:not(:disabled):focus-visible ~ .radio-set__label { --border-color: var(--accent-color); } .radio-set input:disabled ~ .radio-set__label { --border-color: var(--border-color--disabled); --background-color: var(--background-color--disabled); --dot-color: var(--dot-color--disabled); } .radio-set { position: relative; inline-size: var(--border-radius); aspect-ratio: 1/1; overflow: hidden; } .radio-set input { position: absolute; top: -100%; outline: 0 none; } .radio-set__label { ... } .radio-set__label::before { ... } .radio-set__label::after { ... } @media (hover: hover) { .radio-set input:not(:disabled) ~ .radio-set__label:hover { --border-color: var(--accent-color); } } @media (prefers-color-scheme: dark) { .radio-set { ... ... } } </style>
Avoid misunderstanding
Sometimes you might face some embarrassed situations. Such as 「radiobox」or「checkbutton」。
Amazing Supports
Input & Web API
Some <input /> exposed some interesting web api for developers for vivid usage.
<script> // input[type=color] const inputColor = document.querySelector('input[type=color]'); inputColor.showPicker(); // EyeDropper document.querySelector('.button--eyedropper') .addEventListener('click', async () => { const eyeDropper = new EyeDropper(); const result = await eyeDropper.open(); const colorHexValue = result.sRGBHex; } ); // input[type=datetime-local] const inputDatetime = document.querySelector('input[type="datetime-local"]'); inputDatetime.showPicker(); // input[type=number] const inputNumber = document.querySelector('input[type=number]'); document.querySelector('.button--plus') .addEventListener('click', () => { inputNumber.stepUp(); } ); document.querySelector('.button--minus') .addEventListener('click', () => { inputNumber.stepDown(); } ); // input and select() const inputSelectContent = document.querySelector('.input--selectContent'); inputSelectContent.select(); </script>
Validation
form element has many interesting attributes. Web developers could apply them for validation purpose. We never know it's such a piece of cake for validation, ever.
<form action="..."> ... ... <input type="..." value="..." accept=".jpg,.jpeg,.png,.gif,.webp" min="1" max="3000000" minlength="1" maxlength="10" step="5" pattern="https://((youtu.be)|(youtube.com)|(www.youtube.com))/.*" multiple required readonly disabled /> ... ... </form> <script type="module"> const controller = new AbortController(); const { signal } = controller; const form = document.querySelector('form'); const transformErrorMessage = (inputEle) => { let message = ''; const validity = inputEle.validity; const { validityDefault, validityRangeOverflow, validityRangeUnderflow, validityValueMissing } = inputEle.dataset; switch (true) { case validity.valueMissing: message = validityValueMissing || '此為必填欄位'; break; case validity.rangeOverflow: message = validityRangeOverflow || '內容已超過最大值'; break; case validity.rangeUnderflow: message = validityRangeUnderflow || '內容低於最小值'; break; default: // patternMismatch, badInput, customError, stepMismatch, tooLong, tooShort, typeMismatch message = validityDefault || '格式錯誤,請再確認'; break; } return message; } const onInput = (evt) => { const input = evt.target.closest('input'); if (!input) { return; } input.toggleAttribute('invalid', false); }; const onInvalid = (evt) => { evt.preventDefault(); const inputSet = evt.target.closest('.input-set'); const input = evt.target.closest('input'); if (!inputSet) { return; } const errElement = inputSet.querySelector('.input-set__em'); errElement.textContent = transformErrorMessage(evt.target); input.toggleAttribute('invalid', true); }; const onSubmit = (evt) => { evt.preventDefault(); if (form.hasAttribute('inert')) { return; } form.toggleAttribute('inert', true); // native error checks const flag = form.checkValidity(); if (!flag) { // errors occured ... form.toggleAttribute('inert', false); return; } const formData = new FormData(form); const dataObject = Object.fromEntries(formData.entries()); ... ... ... form.toggleAttribute('inert', false); }; // events form.addEventListener('invalid', onInvalid, { signal, capture: true }); form.addEventListener('input', onInput, { signal }); form.addEventListener('submit', onSubmit, { signal }); </script>
Usability
After validation, web developers should think how to enhance usability. There are more and more web api support for this. Sucs as autocapitalize、autocorrect、autocomplete、inputmode and enterkeyhint. Let's take a quick view.
<form action="..."> ... ... <input type="..." value="..." autocapitalize="..." autocorrect="..." autocomplete="..." enterkeyhint="go" inputmode="numeric" /> ... ... </form>
tw.bid.yahoo.com
Wonderful partners
CSS > Logical Properties
With modern CSS supports, web developers could style
<div class="input-set"> <input name="input-name" type="text" placeholder="placeholder" /> <label class="input-set__label"> <span class="input-set__label__span">label</span> </label> <em class="input-set__em"></em> </div> <style> .input-set { --background-color: var(--default-background-color, rgba(var(--white))); --text-color: var(--default-text-color, rgba(var(--batcave))); --border-color: var(--default-border-color, rgba(var(--dolphin))); --err-color: var(--default-err-color, rgba(var(--solo-cup))); --err-text-color: var(--default-err-text-color, rgba(var(--dolphin))); --label-text-color: var(--default-label-text-color, rgba(var(--dolphin))); --theme: var(--default-theme, rgba(var(--dory))); --hover: var(--theme); /* readyonly, disabled */ --background-color-disabled: var(--default-background-color-disabled, rgba(var(--grey-hair))); --text-color-disabled: var(--default-text-color-disabled, rgba(var(--bob))); --border-color-disabled: var(--default-border-color-disabled, rgba(var(--bob))); --placeholder-text-color-normal: transparent; --placeholder-text-color-active: var(--default-placeholder-text-color, rgba(var(--charcoal))); --placeholder-text-color: var(--placeholder-text-color-normal); ... ... } .input-set > input::-webkit-input-placeholder { color: var(--placeholder-text-color); transition: color 100ms ease; will-change: color; } .input-set > input::-moz-placeholder { color: var(--placeholder-text-color); transition: color 100ms ease; will-change: color; } .input-set { position: relative; display: block; } .input-set > input { appearance: none; ... ... } .input-set > input:focus { outline: 0 none; } .input-set__label { ... } .input-set__label__span { ... } .input-set__label::after { ... } .input-set__em { ... } .input-set__em::before { ... } /* focus */ .input-set > input:focus { --border-color: var(--hover); --border-size: var(--border-size-active); } .input-set > input:focus::-webkit-input-placeholder { --placeholder-text-color: var(--placeholder-text-color-active); } .input-set > input:focus::-moz-placeholder { --placeholder-text-color: var(--placeholder-text-color-active); } .input-set > input:focus ~ .input-set__label { color: var(--hover); transform: translateY(-24px) scale(0.85); } .input-set > input:not(:placeholder-shown) ~ .input-set__label { transform: translateY(-24px) scale(0.85); } /* invalid */ .input-set > input:invalid ~ .input-set__em, .input-set > input[invalid] ~ .input-set__em { --err-display: flex; } .input-set > input:invalid, .input-set > input[invalid] { --border-color: var(--err-color); } /* inert, readonly, disabled */ [inert] .input-set, .input-set:has(input[readonly]), .input-set:has(input[disabled]) { --default-text-color: var(--text-color-disabled); --default-border-color: var(--border-color-disabled); --default-background-color: var(--background-color-disabled); } @media (hover: hover) { .input-set:hover { --border-color: var(--hover); } .input-set:hover > input:not(:placeholder-shown) ~ .input-set__label { --label-text-color: var(--hover); } } @media (prefers-color-scheme: dark) { .input-set { ... ... } } </style><input />
easily ever.
CSS > :has()
:has() is so powerful for condition syntax, it could replace some JavaScript supports.
<div class="wrap"> <div class="unit"> <div> <input id="radioA" type="radio" name="my-radio" value="0" /> </div> <p class="hidden-content">hidden information</p> </div> <div class="unit"> <div> <input id="radioB" type="radio" name="my-radio" value="1" /> </div> <p class="hidden-content">hidden information</p> </div> </div> <style> .hidden-content { display: none; } .wrap:has(#radioA:checked) :nth-child(1 of .unit) .hidden-content { display: block; } .wrap:has(#radioB:checked) :nth-child(2 of .unit) .hidden-content { display: block; } </style>
input ?! select ?!
<input /> could associate <datalist /> through attribute list to let input has dropdown menu just like <select />.
<input name="my-input" type="text" placeholder="placeholder" autocomplete="off" list="my-datalist" /> <datalist id="my-datalist"> <option value="option 1">option 1</option> <option value="option 2">option 2</option> <option value="option 3">option 3</option> <option value="option 4">option 4</option> <option value="option 5">option 5</option> </datalist>
details
<details /> provide toggle feature to disaply or hide information. It's suitable for tip or extra information.
<details class="tip"> <summary class="tip__summary"> summary </summary> <p class="tip__content"> content </p> </details>
dialog
The <dialog /> HTML element represents a dialog box or other interactive component, such as a dismissible alert, inspector, or subwindow.
form & inert
It's time to apply 「inert」to freeze or unfreeze <form />.
<style> [inert] :is(input, select) { ... ... } </style> <script> const form = document.querySelector('form'); form.addEventListener('submit', (evt) => { ... ... form.inert = true; // freeze || unfreeze form ... ... } ); </script>
WC. the convenience
Input Assistant
<msc-input-assistant /> is a web component which help user to input wisely. Users could search or pick option with list we provide. Once options are not good enough for them, they can 「add」 their own options for usage.
Image Uploader
<msc-image-uploader /> is a web component based image uploader. Users could pick & upload images by 「file picker」、「drag & drop」and even direct「paste」image content. Besides that, users could also change images sorting through 「drag & drop」or 「keyboard arrow keys」.
Tags Collector
<msc-tags-collector /> is a web component for input tags purpose. Users could key in anything they like (include space). Besides that sorting has already build-in with <msc-tags-collector />. We can go by 「DRAG」and「DROP」or ←、→ to arrange tag orders we like.
Help me write
「OpenAI」 is so powerful for all kinds services. That's why I try to adopt OpenAI to help Y! Auction's seller submit merchandise quickly.
Recap
Thanks, all of you.