Selecting a dropdown option by its visible text label, rather than its value attribute, is one of those small tasks that comes up constantly when working with select elements.
This post covers the jQuery patterns that made it a classic problem, the modern vanilla JavaScript equivalents that need no library at all, and the CSS that turns the browser’s default dropdown into something that actually looks designed.
The HTML
Every example below works against this dropdown:
<label for="colors">What's your favorite color?</label>
<select name="colors" id="colors">
<option value="1">Blue</option>
<option value="2">Red</option>
<option value="3">Green</option>
<option value="4">Yellow</option>
<option value="5">Black</option>
</select>
jQuery Patterns
Plenty of production sites still run jQuery, and these one-liners remain the fastest route there. Find an option by its text label instead of its value:
const option = $("select#colors option:contains('Red')");
Check that it exists, and read its value:
if ( option.length ) {
const value = option.val(); // "2"
}
Read the currently selected option’s text and value:
const selected = $("select#colors option:selected");
const text = selected.text();
const value = selected.val();
Programmatically select an option by its label:
$("select#colors option:contains('Red')").prop('selected', true).trigger('change');
One caution that trips people up: :contains() is a substring match, so “Red” would also match an option labeled “Dark Red”. For exact matching, filter instead:
const exact = $("select#colors option").filter(function () {
return $(this).text().trim() === 'Red';
});
The Same Patterns in Vanilla JavaScript
Every one of those tasks is now a few lines of dependency-free JavaScript, which is the right default for new work:
const select = document.querySelector('#colors');
// Find an option by exact text label
const option = [...select.options].find(o => o.text.trim() === 'Red');
// Read its value if it exists
const value = option?.value; // "2"
// The currently selected option, text and value
const selected = select.selectedOptions[0];
const text = selected.text;
const current = select.value;
// Select an option by label, firing the same events a user would
if (option) {
select.value = option.value;
select.dispatchEvent(new Event('change', { bubbles: true }));
}
The details worth knowing: select.options and selectedOptions are live HTMLCollections, spreading into an array gives you every array method, and dispatching the change event keeps frameworks and other listeners in sync the way .trigger('change') did in jQuery.
Styling the Dropdown Properly
Native selects are the last form control most sites leave unstyled, and the default look drags an otherwise polished form down. The reliable cross-browser approach: strip the native appearance, draw your own chevron as an inline SVG background, and manage the states yourself.
.select {
--select-border: #cfd4dc;
--select-focus: #2a498e;
--select-ink: #14120e;
appearance: none;
-webkit-appearance: none;
width: 100%;
padding: 0.65em 2.6em 0.65em 0.9em;
font: inherit;
color: var(--select-ink);
line-height: 1.3;
background-color: #fff;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath fill='none' stroke='%236d6659' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' d='m3 6 5 5 5-5'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: right 0.8em center;
background-size: 1em;
border: 1px solid var(--select-border);
border-radius: 6px;
cursor: pointer;
transition: border-color 140ms ease, box-shadow 140ms ease;
}
.select:hover {
border-color: color-mix(in srgb, var(--select-focus) 45%, var(--select-border));
}
.select:focus-visible {
outline: none;
border-color: var(--select-focus);
box-shadow: 0 0 0 3px color-mix(in srgb, var(--select-focus) 22%, transparent);
}
.select:disabled {
opacity: 0.55;
cursor: not-allowed;
background-color: #f4f2ec;
}
The same thing as SCSS, structured for a design system with tokens:
$select-border: #cfd4dc;
$select-focus: #2a498e;
$select-ink: #14120e;
.select {
appearance: none;
width: 100%;
padding: 0.65em 2.6em 0.65em 0.9em;
font: inherit;
color: $select-ink;
background: #fff no-repeat right 0.8em center / 1em;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath fill='none' stroke='%236d6659' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' d='m3 6 5 5 5-5'/%3E%3C/svg%3E");
border: 1px solid $select-border;
border-radius: 6px;
cursor: pointer;
transition: border-color 140ms ease, box-shadow 140ms ease;
&:hover {
border-color: mix($select-focus, $select-border, 45%);
}
&:focus-visible {
outline: none;
border-color: $select-focus;
box-shadow: 0 0 0 3px rgba($select-focus, 0.22);
}
&:disabled {
opacity: 0.55;
cursor: not-allowed;
}
}
Two notes on this approach. The chevron is a data-URI SVG, so there’s no icon font or extra request, and recoloring it means editing the stroke value in the URI. And the padding on the right exists to keep long option labels from running under the arrow.
The Future: Fully Customizable Selects in CSS
Chromium browsers now ship appearance: base-select, which opts a select into a new mode where the popup itself, the options, and even arbitrary markup inside options become stylable with plain CSS through pseudo-elements like ::picker(select):
select,
::picker(select) {
appearance: base-select;
}
::picker(select) {
border: 1px solid #cfd4dc;
border-radius: 8px;
box-shadow: 0 12px 30px rgba(0, 0, 0, 0.12);
}
option:checked {
font-weight: 600;
}
Treat it as progressive enhancement for now: Firefox and Safari fall back to the classic rendering, and the technique above remains the baseline that works everywhere.
Conclusion
The jQuery selector patterns that made this post are still valid on the sites that run jQuery, the vanilla equivalents are what I reach for in anything new, and either way the styling layer is where a dropdown stops feeling like a browser default. Copy the CSS block, swap the three custom property values for your brand, and the least-loved control in the form catches up with the rest of your design.