The <select>
element gives a menu of options for a user and is implemented by the DOM HTMLSelectElement
Interface. You can add the <multiple>
attributes to the select
element and add multiple options. The examples in this section use HTML, CSS, and JavasScript to customize <select>
styles.
Examples
Basic select
The following example creates a very simple dropdown menu, the second option of which is selected by default.
<!-- The second value will be selected initially --> <select name="choice"> <option value="first">First Value</option> <option value="second" selected>Second Value</option> <option value="third">Third Value</option> </select>
Result
Advanced select with multiple features
The follow example is more complex, showing off more features you can use on a <select>
element:
<label> Please choose one or more pets: <select name="pets" multiple size="4"> <optgroup label="4-legged pets"> <option value="dog">Dog</option> <option value="cat">Cat</option> <option value="hamster" disabled>Hamster</option> </optgroup> <optgroup label="Flying pets"> <option value="parrot">Parrot</option> <option value="macaw">Macaw</option> <option value="albatross">Albatross</option> </optgroup> </select> </label>
Result
You'll see that:
- Multiple options are selectable because we've included the
multiple
attribute. - The
size
attribute causes only 4 lines to display at a time; you can scroll to view all the options. - We've included
<optgroup>
elements to divide the options up into different groups. This is a purely visual grouping, its visualization generally consists of the group name being bolded, and the options being indented. - The "Hamster" option includes a
disabled
attribute and therefore can't be selected at all.
Customizing select styles
This example shows how you could use some CSS and JavaScript to provide extensive custom styling for a <select>
box.
This example basically:
- Clones the
<select>
's context (the<option>
elements) in a parent wrapper and reimplements the standard expected behavior using additional HTML elements and JavaScript. This includes basic tab behavior to provide keyboard accessibility. - Maps some standards native
attributes
todata-attributes
of the new elements in order to manage state and CSS.
Note: Not all native features are supported, it's a Proof of Concept. IT starts from standard HTML but the same results can be achieved starting from JSON data, custom HTML, or other solutions.
HTML
<form> <fieldset> <legend>Standard controls</legend> <select name="1A" id="select" autocomplete="off" required> <option>Carrots</option> <option>Peas</option> <option>Beans</option> <option>Pneumonoultramicroscopicsilicovolcanoconiosis</option> </select> </fieldset> <fieldset id="custom"> <legend>Custom controls</legend> <select name="2A" id="select" autocomplete="off" required> <option>Carrots</option> <option>Peas</option> <option>Beans</option> <option>Pneumonoultramicroscopicsilicovolcanoconiosis</option> </select> </fieldset> </form>
CSS
body { font-family: Cambria, Cochin, Georgia, Times, "Times New Roman", serif; } .select:focus { border-color: blue; } html body form fieldset#custom div.select[data-multiple] div.header { display: none; } html body form fieldset#custom div.select div.header { content: "↓"; display: flex; flex: 1; align-items: center; padding: 0; position: relative; width: auto; box-sizing: border-box; border-width: 1px; border-style: inherit; border-color: inherit; border-radius: inherit; } html body form fieldset#custom div.select div.header::after { content: "↓"; align-self: stretch; display: flex; align-content: center; justify-content: center; justify-items: center; align-items: center; padding: 0.5em; } html body form fieldset#custom div.select div.header:hover::after { background-color: blue; } .select .header select { appearance: none; font-family: inherit; font-size: inherit; padding: 0; border-width: 0; width: 100%; flex: 1; display: none; } .select .header select optgroup { display: none; } .select select div.option { display: none; } html body form fieldset#custom div.select { user-select: none; box-sizing: border-box; position: relative; border-radius: 4px; border-style: solid; border-width: 0; border-color: gray; width: auto; display: inline-block; } html body form fieldset#custom div.select:focus, html body form fieldset#custom div.select:hover { border-color: blue; } html body form fieldset#custom div.select[data-open] { border-bottom-left-radius: 0; border-bottom-right-radius: 0; } html body form fieldset#custom div.select[data-open] datalist { display: initial; } html body form fieldset#custom div.select datalist { appearance: none; position: absolute; border-style: solid; border-width: 1px; border-color: gray; left: 0; display: none; width: 100%; box-sizing: border-box; z-index: 2; border-bottom-left-radius: 4px; border-bottom-right-radius: 4px; } html body form fieldset#custom div.select datalist div.option { background-color: white; margin-bottom: 1px; cursor: pointer; padding: 0.5em; border-width: 0; } html body form fieldset#custom div.select datalist div.option:hover, html body form fieldset#custom div.select datalist div.option:focus, html body form fieldset#custom div.select datalist div.option:checked { background-color: blue; color: white; } html body form fieldset#custom div.select div.optgroup div.option[data-disabled] { color: gray; } html body form fieldset#custom div.select div.optgroup div.option[data-checked] { background-color: blue; color: white; } html body form fieldset#custom div.select div.optgroup div.label { font-weight: bold; } html body form fieldset#custom div.select div.optgroup div.option div.label { font-weight: normal; padding: 0.25em; } html body form fieldset#custom div.select div.header span { flex: 1; padding: 0.5em; }
JavaScript
const selects = custom.querySelectorAll("select"); for (const select of selects) { const div = document.createElement("div"); const header = document.createElement("div"); const datalist = document.createElement("datalist"); const optgroups = select.querySelectorAll("optgroup"); const span = document.createElement("span"); const options = select.options; const parent = select.parentElement; const multiple = select.hasAttribute("multiple"); function onclick(e) { const disabled = this.hasAttribute("data-disabled"); select.value = this.dataset.value; span.innerText = this.dataset.label; if (disabled) return; if (multiple) { if (e.shiftKey) { const checked = this.hasAttribute("data-checked"); if (checked) { this.removeAttribute("data-checked"); } else { this.setAttribute("data-checked", ""); } } else { const options = div.querySelectorAll(".option"); for (let i = 0; i < options.length; i++) { const option = options[i]; option.removeAttribute("data-checked"); } this.setAttribute("data-checked", ""); } } } function onkeyup(e) { e.preventDefault(); e.stopPropagation(); if (e.keyCode === 13) { this.click(); } } div.classList.add("select"); header.classList.add("header"); div.tabIndex = 1; select.tabIndex = -1; span.innerText = select.label; header.appendChild(span); for (const attribute of select.attributes) { div.dataset[attribute.name] = attribute.value; } for (let i = 0; i < options.length; i++) { const option = document.createElement("div"); const label = document.createElement("div"); const o = options[i]; for (const attribute of o.attributes) { option.dataset[attribute.name] = attribute.value; } option.classList.add("option"); label.classList.add("label"); label.innerText = o.label; option.dataset.value = o.value; option.dataset.label = o.label; option.onclick = onclick; option.onkeyup = onkeyup; option.tabIndex = i + 1; option.appendChild(label); datalist.appendChild(option); } div.appendChild(header); for (const o of optgroups) { const optgroup = document.createElement("div"); const label = document.createElement("div"); const options = o.querySelectorAll("option"); Object.assign(optgroup, o); optgroup.classList.add("optgroup"); label.classList.add("label"); label.innerText = o.label; optgroup.appendChild(label); div.appendChild(optgroup); for (const o of options) { const option = document.createElement("div"); const label = document.createElement("div"); for (const attribute of o.attributes) { option.dataset[attribute.name] = attribute.value; } option.classList.add("option"); label.classList.add("label"); label.innerText = o.label; option.tabIndex = i + 1; option.dataset.value = o.value; option.dataset.label = o.label; option.onclick = onclick; option.onkeyup = onkeyup; option.tabIndex = i + 1; option.appendChild(label); optgroup.appendChild(option); } } div.onclick = (e) => { e.preventDefault(); }; parent.insertBefore(div, select); header.appendChild(select); div.appendChild(datalist); datalist.style.top = `${header.offsetTop + header.offsetHeight}px`; div.onclick = (e) => { if (!multiple) { const open = div.hasAttribute("data-open"); e.stopPropagation(); if (open) { div.removeAttribute("data-open"); } else { div.setAttribute("data-open", ""); } } }; div.onkeyup = (event) => { event.preventDefault(); if (event.keyCode === 13) { div.click(); } }; document.addEventListener("click", (e) => { if (div.hasAttribute("data-open")) { div.removeAttribute("data-open"); } }); const width = Math.max( ...Array.from(options).map((e) => { span.innerText = e.label; return div.offsetWidth; }), ); console.log(width); div.style.width = `${width}px`; } document.forms[0].onsubmit = (e) => { const data = new FormData(this); e.preventDefault(); submit.innerText = JSON.stringify([...data.entries()]); };