Customize select elements

Web developers could style form elemets with CSS pseudo class & pseudo elements and other modern CSS properties.

normal

disabled

invalid

error occured

HTML structure

<div class="input-set"> <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> <label class="input-set__label"> <span class="input-set__label__span">label</span> </label> <em class="input-set__em"></em> </div>

Style Customization

Apply accent-color to <input />'s style.

<style> .input-set { --icon-warn: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIGhlaWdodD0nMjQnIHdpZHRoPScyNCc+PHBhdGggZD0nTTAgMGgyNHYyNEgweicgZmlsbD0nbm9uZScvPjxwYXRoIGZpbGw9JyNlYjBmMjknIGQ9J00xMiAyQzYuNDggMiAyIDYuNDggMiAxMnM0LjQ4IDEwIDEwIDEwIDEwLTQuNDggMTAtMTBTMTcuNTIgMiAxMiAyem0xIDE1aC0ydi0yaDJ2MnptMC00aC0yVjdoMnY2eicvPjwvc3ZnPg==) no-repeat 50% 50%/cover; --icon-arrow-down: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHdpZHRoPScyNCcgaGVpZ2h0PScyNCc+PGcgZmlsbD0nbm9uZScgZmlsbC1ydWxlPSdldmVub2RkJz48cGF0aCBmaWxsPScjNkU3NzgwJyBkPSdNMTguOTI1IDkuNjE0TDEyLjQ5IDE2IDYuMDU1IDkuNjE1YS45MzcuOTM3IDAgMDEwLTEuMzM4Ljk1OC45NTggMCAwMTEuMzQ3IDBsNS4wODggNS4wNDkgNS4wODgtNS4wNDlhLjk1Ni45NTYgMCAwMTEuMzQ3IDAgLjk0Ljk0IDAgMDEwIDEuMzM3eicvPjxwYXRoIGQ9J00yNCAwdjI0SC45OFYweicvPjwvZz48L3N2Zz4=) no-repeat 50% 50%/24px 24px; --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); --border-radius: var(--default-border-radius, 4px); --input-block-size: var(--default-input-block-size, 48px); --input-line-height: var(--default-input-line-height, 46px); --padding-inline-start: var(--default-padding-inline-start, 12px); --padding-inline-end: var(--default-padding-inline-end, 12px); --padding-inline: var(--padding-inline-start) var(--padding-inline-end); --border-size-normal: 1px; --border-size-active: 2px; --border-size: var(--border-size-normal); --err-display: none; } /* select */ .input-set--select select { padding-inline-end: calc(var(--padding-inline-end) * 2 + 24px); } .input-set--select::after{position:absolute;inset-inline-end:var(--padding-inline-end);inset-block-start:12px;inline-size:24px;block-size:24px;content:'';background:var(--icon-arrow-down);pointer-events:none;} /* blank */ .input-set--blank { border-radius: var(--border-radius); box-sizing: border-box; background-color: var(--background-color); box-shadow: inset calc(var(--border-size) * -1) calc(var(--border-size) * -1) 0 var(--border-color), inset var(--border-size) var(--border-size) 0 var(--border-color); outline: 0 none; } .input-set--blank:focus-within { --border-color: var(--hover); --border-size: var(--border-size-active); } .input-set > :is(input, select, textarea)::-webkit-input-placeholder{color:var(--placeholder-text-color);transition:color 100ms ease;will-change:color;} .input-set > :is(input, select, textarea)::-moz-placeholder{color:var(--placeholder-text-color);transition:color 100ms ease;will-change:color;} .input-set{position:relative;display:block;} .input-set > :is(input, select, textarea) { font-size:1em; inline-size:100%; padding-inline:var(--padding-inline); block-size:var(--input-block-size); border-radius:var(--border-radius); box-sizing:border-box; display:block; appearance:none; border:0 none; resize:none; outline: 0 none; line-height: var(--input-line-height); color: var(--text-color); background-color: var(--background-color); box-shadow: inset calc(var(--border-size) * -1) calc(var(--border-size) * -1) 0 var(--border-color), inset var(--border-size) var(--border-size) 0 var(--border-color); transition: box-shadow 100ms ease; } .input-set > :is(input, select, textarea):focus{outline: 0 none;} .input-set__label{position:absolute;inset-inline-start:6px;inset-block-start:16px;inline-size:fit-content;font-size:1em;color:var(--label-text-color);line-height:1;padding-inline:6px;display:block;pointer-events:none;transition:transform 100ms ease,color 100ms ease,background-color 100ms ease;will-change:transform,color,background-color;} .input-set__label__span{position:relative;z-index:1;} .input-set__label::after{position:absolute;inset-inline-start:0;inset-block-start:50%;inline-size:100%;block-size:3.53px;background-color:var(--background-color);content:'';} .input-set__em{font-size:14px;line-height:1;color:var(--err-text-color);padding-inline-start:var(--padding-inline-start);margin-block-start:8px;display:var(--err-display);align-items:center;gap:6px;} .input-set__em::before{inline-size:14px;block-size:14px;background:var(--icon-warn);content:'';} .input-set--hide-label { --placeholder-text-color-normal: var(--placeholder-text-color-active); } .input-set--hide-label .input-set__label{display:none;} /* focus */ .input-set > :is(input, select, textarea):focus { --border-color: var(--hover); --border-size: var(--border-size-active); } .input-set > :is(input, select, textarea):focus::-webkit-input-placeholder { --placeholder-text-color: var(--placeholder-text-color-active); } .input-set > :is(input, select, textarea):focus::-moz-placeholder { --placeholder-text-color: var(--placeholder-text-color-active); } .input-set > :is(input, select, textarea):focus ~ .input-set__label { color: var(--hover); transform: translateY(-24px) scale(0.85); } .input-set > :is(input, select, textarea):not(:placeholder-shown) ~ .input-set__label { transform: translateY(-24px) scale(0.85); } /* invalid */ .input-set > :is(input, select, textarea):invalid~.input-set__em, .input-set > :is(input, select, textarea)[invalid]~.input-set__em { --err-display: flex; } .input-set > :is(input, select, textarea):invalid, .input-set > :is(input, select, textarea)[invalid] { --border-color: var(--err-color); } /* inert, readonly, disabled */ [inert] .input-set, .input-set:has(:is(input, select, textarea)[readonly]), .input-set:has(:is(input, select, textarea)[disabled]) { --default-text-color: var(--text-color-disabled); --default-border-color: var(--border-color-disabled); --default-background-color: var(--background-color-disabled); } /* select */ .input-set--select select { padding-inline-end: calc(var(--padding-inline-end) * 2 + 24px); } .input-set--select::after{position:absolute;inset-inline-end:var(--padding-inline-end);inset-block-start:50%;inline-size:24px;block-size:24px;content:'';margin-block-start:-12px;background:var(--icon-arrow-down);pointer-events:none;} @media (hover: hover) { .input-set:hover { --border-color: var(--hover); } .input-set:hover > :is(input, select, textarea):not(:placeholder-shown)~.input-set__label { --label-text-color: var(--hover); } } @media (prefers-color-scheme: dark) { .input-set { --default-background-color: rgba(var(--batcave)); --default-text-color: rgba(var(--grey-hair)); --default-border-color: rgba(var(--charcoal)); --default-placeholder-text-color: rgba(var(--dolphin)); --default-label-text-color: rgba(var(--gandalf)); --default-placeholder-color: rgba(var(--dolphin)); --default-text-color-disabled: rgba(var(--shark)); --default-border-color-disabled: rgba(var(--charcoal)); --default-background-color-disabled: rgba(var(--batcave)); } } </style>

Events

Add input and invalid events to main form to display error effect or not.

<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 select = evt.target.closest('select'); if (!select) { return; } select.toggleAttribute('invalid', false); }; const onInvalid = (evt) => { evt.preventDefault(); const inputSet = evt.target.closest('.input-set'); const select = evt.target.closest('select'); if (!inputSet) { return; } const errElement = inputSet.querySelector('.input-set__em'); errElement.textContent = transformErrorMessage(evt.target); select.toggleAttribute('invalid', true); } // events form.addEventListener('invalid', onInvalid, { signal, capture: true }); form.addEventListener('input', onInput, { signal }); </script>