Open demo

CSS code

HTML
<div><span class="lbl">Search</span>
<div class="f">
<label for="p1">Search templates</label>
<div class="search"><input id="p1" name="q" type="search" autocomplete="off" placeholder="Try “dashboard”"></div>
</div>
</div>
<div><span class="lbl">Range</span>
<div class="f">
<label for="p2">Monthly budget <span aria-hidden="true">— <output id="p2-out" for="p2">£1,200</output></span></label>
<input id="p2" name="budget" type="range" min="0" max="5000" step="50" value="1200" aria-valuetext="£1,200" data-range-output="p2-out">
</div>
</div>
<div><span class="lbl">File drop area</span>
<div class="f">
<label for="p3">Project files</label>
<div class="drop" data-drop>
<input id="p3" name="files" type="file" multiple accept=".png,.jpg,.jpeg,.pdf" aria-describedby="p3-hint p3-files">
<b>Drop files here, or browse</b>
<span id="p3-hint">PNG, JPG or PDF up to 10MB</span>
<span id="p3-files" class="files" aria-live="polite"></span>
</div>
</div>
</div>
<div><span class="lbl">Number stepper</span>
<div class="f">
<label for="p4">Licence seats</label>
<div class="step" data-stepper>
<button type="button" data-step="-1" aria-controls="p4" aria-label="Remove a seat">−</button>
<input id="p4" name="seats" type="number" inputmode="numeric" min="1" max="50" value="3">
<button type="button" data-step="1" aria-controls="p4" aria-label="Add a seat">+</button>
</div>
<span class="sr-only" aria-live="polite" data-stepper-status></span>
</div>
</div>
CSS
/* Search, range, file drop and number stepper fields. */
.f{--f-text:#0f172a;--f-label:#334155;--f-hint:#64748b;--f-bg:#fff;--f-border:#8492a6;--f-focus:#2563eb;
  --f-ring:#dbeafe;--f-track:#cbd5e1;--f-radius:9px;display:flex;flex-direction:column;gap:7px;max-width:360px}
.f label{font-size:.82rem;font-weight:600;color:var(--f-label)}
.f input{box-sizing:border-box;width:100%;font:inherit;font-size:.92rem;padding:11px 14px;color:var(--f-text);
  background:var(--f-bg);border:1px solid var(--f-border);border-radius:var(--f-radius);transition:border-color .16s,box-shadow .16s}
.f input::placeholder{color:var(--f-hint)}
.f input:focus{outline:0;border-color:var(--f-focus);box-shadow:0 0 0 3px var(--f-ring)}
.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}
/* search: magnifier drawn with two pseudo-elements */
.search{position:relative}
.search input{padding-left:42px;border-radius:999px}
.search::before{content:"";box-sizing:border-box;position:absolute;left:16px;top:12px;width:13px;height:13px;
  border:2px solid var(--f-hint);border-radius:50%;pointer-events:none}
.search::after{content:"";position:absolute;left:27px;top:25px;width:7px;height:2px;background:var(--f-hint);
  transform:rotate(45deg);pointer-events:none}
/* range */
.f input[type=range]{-webkit-appearance:none;appearance:none;height:6px;padding:0;border:0;background:var(--f-track);border-radius:99px}
.f input[type=range]::-webkit-slider-thumb{-webkit-appearance:none;width:20px;height:20px;border-radius:50%;
  background:var(--f-focus);cursor:pointer;box-shadow:0 1px 4px rgba(15,23,42,.3)}
.f input[type=range]::-moz-range-thumb{width:20px;height:20px;border:0;border-radius:50%;background:var(--f-focus);cursor:pointer}
.f input[type=range]:focus{box-shadow:none}
.f input[type=range]:focus-visible::-webkit-slider-thumb{box-shadow:0 0 0 1px #fff,0 0 0 4px var(--f-focus)}
.f input[type=range]:focus-visible::-moz-range-thumb{box-shadow:0 0 0 1px #fff,0 0 0 4px var(--f-focus)}
/* file drop: the real file input covers the area, so clicking and dropping both reach it */
.drop{position:relative;border:2px dashed var(--f-border);border-radius:12px;padding:30px;text-align:center;
  background:var(--f-bg);transition:border-color .16s,background-color .16s,box-shadow .16s}
.drop input[type=file]{position:absolute;inset:0;width:100%;height:100%;padding:0;border:0;opacity:0;cursor:pointer}
.drop:hover,.drop.is-over{border-color:var(--f-focus);background:#eff6ff}
.drop:focus-within{border-color:var(--f-focus);box-shadow:0 0 0 3px var(--f-ring)}
.drop b{display:block;font-size:.92rem;margin-bottom:5px}
.drop span{font-size:.79rem;color:var(--f-hint)}
.drop .files{display:block;margin-top:8px;color:var(--f-text);font-weight:600;overflow-wrap:anywhere}
.drop .files:empty{display:none}
/* number stepper */
.step{display:flex;max-width:170px}
.step button{font:inherit;font-size:1.05rem;width:42px;flex:none;border:1px solid var(--f-border);background:#f8fafc;
  color:var(--f-label);cursor:pointer}
.step button:first-child{border-radius:var(--f-radius) 0 0 var(--f-radius);border-right:0}
.step button:last-child{border-radius:0 var(--f-radius) var(--f-radius) 0;border-left:0}
.step button:hover{background:#eef2f7}
.step button:focus-visible{position:relative;outline:2px solid var(--f-focus);outline-offset:2px}
.f .step input{min-width:0;text-align:center;border-radius:0;-moz-appearance:textfield;appearance:textfield}
.step input::-webkit-inner-spin-button,.step input::-webkit-outer-spin-button{-webkit-appearance:none;margin:0}
@media (prefers-reduced-motion:reduce){.f input,.drop{transition:none}}
JavaScript
// Range: show the value beside the label and give screen readers the same text.
document.querySelectorAll('input[data-range-output]').forEach(function (range) {
  var out = document.getElementById(range.dataset.rangeOutput);
  var update = function () {
    var text = '£' + Number(range.value).toLocaleString('en-GB');
    out.textContent = text; range.setAttribute('aria-valuetext', text);
  };
  range.addEventListener('input', update); update();
});
// File drop: list the chosen files, flag any over 10MB, highlight while dragging over.
document.querySelectorAll('[data-drop]').forEach(function (drop) {
  var input = drop.querySelector('input[type=file]'), list = drop.querySelector('.files');
  ['dragenter', 'dragover'].forEach(function (e) { drop.addEventListener(e, function () { drop.classList.add('is-over'); }); });
  ['dragleave', 'drop'].forEach(function (e) { drop.addEventListener(e, function () { drop.classList.remove('is-over'); }); });
  input.addEventListener('change', function () {
    var tooBig = false;
    list.textContent = Array.prototype.map.call(input.files, function (f) {
      if (f.size > 10485760) { tooBig = true; return f.name + ' (over 10MB)'; }
      return f.name;
    }).join(', ');
    input.setCustomValidity(tooBig ? 'Each file must be 10MB or smaller.' : '');
  });
});
// Stepper: the buttons use the input's own min, max and step, and announce the new value.
document.querySelectorAll('[data-stepper]').forEach(function (step) {
  var input = step.querySelector('input'), status = step.parentElement.querySelector('[data-stepper-status]');
  step.querySelectorAll('button[data-step]').forEach(function (btn) {
    btn.addEventListener('click', function () {
      if (btn.dataset.step > 0) input.stepUp(); else input.stepDown();
      input.dispatchEvent(new Event('input', { bubbles: true }));
      if (status) status.textContent = input.value + (input.value === '1' ? ' seat' : ' seats');
    });
  });
});

Bootstrap 5 code

Requires: Bootstrap 5.3.8 CSS

HTML
<div><span class="lbl">Search</span>
<div class="fx">
<label for="p1" class="form-label">Search templates</label>
<div class="search"><input id="p1" name="q" class="form-control rounded-pill" type="search" autocomplete="off" placeholder="Try “dashboard”"></div>
</div>
</div>
<div><span class="lbl">Range</span>
<div class="fx">
<label for="p2" class="form-label">Monthly budget <span aria-hidden="true">— <output id="p2-out" for="p2">£1,200</output></span></label>
<input id="p2" name="budget" class="form-range" type="range" min="0" max="5000" step="50" value="1200" aria-valuetext="£1,200" data-range-output="p2-out">
</div>
</div>
<div><span class="lbl">File drop area</span>
<div class="fx">
<label for="p3" class="form-label">Project files</label>
<div class="drop" data-drop>
<input id="p3" name="files" type="file" multiple accept=".png,.jpg,.jpeg,.pdf" aria-describedby="p3-hint p3-files">
<b>Drop files here, or browse</b>
<span id="p3-hint">PNG, JPG or PDF up to 10MB</span>
<span id="p3-files" class="files" aria-live="polite"></span>
</div>
</div>
</div>
<div><span class="lbl">Number stepper</span>
<div class="fx">
<label for="p4" class="form-label">Licence seats</label>
<div class="input-group step" data-stepper>
<button class="btn" type="button" data-step="-1" aria-controls="p4" aria-label="Remove a seat">−</button>
<input id="p4" name="seats" class="form-control" type="number" inputmode="numeric" min="1" max="50" value="3">
<button class="btn" type="button" data-step="1" aria-controls="p4" aria-label="Add a seat">+</button>
</div>
<span class="visually-hidden" aria-live="polite" data-stepper-status></span>
</div>
</div>
CSS
/* Bootstrap .form-control, .form-range and .input-group + .btn, restyled through --bs-* variables on .fx.
   The drop area is custom: Bootstrap has no drop-zone component. */
.fx{--bs-body-color:#0f172a;--bs-body-bg:#fff;--bs-border-color:#8492a6;--bs-border-radius:9px;
  --bs-secondary-color:#64748b;--f-label:#334155;--f-focus:#2563eb;--f-ring:#dbeafe;--f-track:#cbd5e1;max-width:360px}
.fx .form-label{display:block;margin-bottom:7px;font-size:.82rem;font-weight:600;color:var(--f-label)}
.fx .form-control{padding:11px 14px;font-size:.92rem;line-height:normal;transition:border-color .16s,box-shadow .16s}
.fx .form-control:focus{border-color:var(--f-focus);box-shadow:0 0 0 3px var(--f-ring)}
/* search: magnifier drawn with two pseudo-elements on the wrapper */
.fx .search{position:relative}
.fx .search .form-control{padding-left:42px}
.fx .search::before{content:"";position:absolute;z-index:1;left:16px;top:12px;width:13px;height:13px;
  border:2px solid var(--bs-secondary-color);border-radius:50%;pointer-events:none}
.fx .search::after{content:"";position:absolute;z-index:1;left:27px;top:25px;width:7px;height:2px;
  background:var(--bs-secondary-color);transform:rotate(45deg);pointer-events:none}
/* range */
.fx .form-range{display:block;height:6px}
.fx .form-range::-webkit-slider-runnable-track{height:6px;background:var(--f-track);border-radius:99px}
.fx .form-range::-moz-range-track{height:6px;background:var(--f-track);border-radius:99px}
.fx .form-range::-webkit-slider-thumb{width:20px;height:20px;margin-top:-7px;background:var(--f-focus);box-shadow:0 1px 4px rgba(15,23,42,.3);cursor:pointer}
.fx .form-range::-moz-range-thumb{width:20px;height:20px;background:var(--f-focus);cursor:pointer}
.fx .form-range:focus::-webkit-slider-thumb{box-shadow:0 1px 4px rgba(15,23,42,.3)}
.fx .form-range:focus::-moz-range-thumb{box-shadow:none}
.fx .form-range:focus-visible::-webkit-slider-thumb{box-shadow:0 0 0 1px #fff,0 0 0 4px var(--f-focus)}
.fx .form-range:focus-visible::-moz-range-thumb{box-shadow:0 0 0 1px #fff,0 0 0 4px var(--f-focus)}
/* file drop: the real file input covers the area, so clicking and dropping both reach it */
.fx .drop{position:relative;border:2px dashed var(--bs-border-color);border-radius:12px;padding:30px;text-align:center;
  background:var(--bs-body-bg);transition:border-color .16s,background-color .16s,box-shadow .16s}
.fx .drop input[type=file]{position:absolute;inset:0;width:100%;height:100%;opacity:0;cursor:pointer}
.fx .drop:hover,.fx .drop.is-over{border-color:var(--f-focus);background:#eff6ff}
.fx .drop:focus-within{border-color:var(--f-focus);box-shadow:0 0 0 3px var(--f-ring)}
.fx .drop b{display:block;font-size:.92rem;margin-bottom:5px}
.fx .drop span{font-size:.79rem;color:var(--bs-secondary-color)}
.fx .drop .files{display:block;margin-top:8px;color:var(--bs-body-color);font-weight:600;overflow-wrap:anywhere}
.fx .drop .files:empty{display:none}
/* stepper: .input-group with two .btn */
.fx .input-group.step{max-width:170px;flex-wrap:nowrap}
.fx .step .btn{--bs-btn-padding-x:0;--bs-btn-font-size:1.05rem;--bs-btn-color:var(--f-label);--bs-btn-bg:#f8fafc;
  --bs-btn-border-color:var(--bs-border-color);--bs-btn-hover-color:var(--f-label);--bs-btn-hover-bg:#eef2f7;
  --bs-btn-hover-border-color:var(--bs-border-color);--bs-btn-active-bg:#e2e8f0;--bs-btn-active-color:var(--f-label);
  --bs-btn-active-border-color:var(--bs-border-color);--bs-btn-focus-box-shadow:none;width:42px;flex:none}
.fx .step .btn:focus-visible{z-index:6;background-color:var(--bs-btn-bg);outline:2px solid var(--f-focus);outline-offset:2px}
.fx .step .form-control{min-width:0;text-align:center;-moz-appearance:textfield;appearance:textfield}
.fx .step .form-control::-webkit-inner-spin-button,.fx .step .form-control::-webkit-outer-spin-button{-webkit-appearance:none;margin:0}
@media (prefers-reduced-motion:reduce){.fx .form-control,.fx .drop,.fx .btn{transition:none}}
JavaScript
// Range: show the value beside the label and give screen readers the same text.
document.querySelectorAll('input[data-range-output]').forEach(function (range) {
  var out = document.getElementById(range.dataset.rangeOutput);
  var update = function () {
    var text = '£' + Number(range.value).toLocaleString('en-GB');
    out.textContent = text; range.setAttribute('aria-valuetext', text);
  };
  range.addEventListener('input', update); update();
});
// File drop: list the chosen files, flag any over 10MB, highlight while dragging over.
document.querySelectorAll('[data-drop]').forEach(function (drop) {
  var input = drop.querySelector('input[type=file]'), list = drop.querySelector('.files');
  ['dragenter', 'dragover'].forEach(function (e) { drop.addEventListener(e, function () { drop.classList.add('is-over'); }); });
  ['dragleave', 'drop'].forEach(function (e) { drop.addEventListener(e, function () { drop.classList.remove('is-over'); }); });
  input.addEventListener('change', function () {
    var tooBig = false;
    list.textContent = Array.prototype.map.call(input.files, function (f) {
      if (f.size > 10485760) { tooBig = true; return f.name + ' (over 10MB)'; }
      return f.name;
    }).join(', ');
    input.setCustomValidity(tooBig ? 'Each file must be 10MB or smaller.' : '');
  });
});
// Stepper: the buttons use the input's own min, max and step, and announce the new value.
document.querySelectorAll('[data-stepper]').forEach(function (step) {
  var input = step.querySelector('input'), status = step.parentElement.querySelector('[data-stepper-status]');
  step.querySelectorAll('button[data-step]').forEach(function (btn) {
    btn.addEventListener('click', function () {
      if (btn.dataset.step > 0) input.stepUp(); else input.stepDown();
      input.dispatchEvent(new Event('input', { bubbles: true }));
      if (status) status.textContent = input.value + (input.value === '1' ? ' seat' : ' seats');
    });
  });
});

Tailwind 4 code

Requires: Tailwind CSS 4

HTML
<div><span class="mb-4 block text-[.72rem] tracking-[.12em] text-[#64748b] uppercase">Search</span>
<div class="flex max-w-[360px] flex-col gap-[7px]">
<label for="p1" class="text-[.82rem] font-semibold text-[#334155]">Search templates</label>
<div class="relative before:pointer-events-none before:absolute before:top-3 before:left-4 before:size-[13px] before:rounded-full before:border-2 before:border-[#64748b] after:pointer-events-none after:absolute after:top-[25px] after:left-[27px] after:h-[2px] after:w-[7px] after:rotate-45 after:bg-[#64748b]"><input id="p1" name="q" type="search" autocomplete="off" placeholder="Try “dashboard”" class="w-full rounded-full border border-[#8492a6] bg-white py-[11px] pr-[14px] pl-[42px] text-[.92rem] text-[#0f172a] placeholder:text-[#64748b] transition-[border-color,box-shadow] duration-[160ms] focus:border-[#2563eb] focus:shadow-[0_0_0_3px_#dbeafe] focus:outline-none motion-reduce:transition-none"></div>
</div>
</div>
<div><span class="mb-4 block text-[.72rem] tracking-[.12em] text-[#64748b] uppercase">Range</span>
<div class="flex max-w-[360px] flex-col gap-[7px]">
<label for="p2" class="text-[.82rem] font-semibold text-[#334155]">Monthly budget <span aria-hidden="true">— <output id="p2-out" for="p2">£1,200</output></span></label>
<input id="p2" name="budget" type="range" min="0" max="5000" step="50" value="1200" aria-valuetext="£1,200" data-range-output="p2-out" class="h-[6px] w-full cursor-pointer appearance-none rounded-full bg-[#cbd5e1] focus:outline-none [&::-webkit-slider-thumb]:size-5 [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:bg-[#2563eb] [&::-webkit-slider-thumb]:shadow-[0_1px_4px_rgba(15,23,42,.3)] [&::-moz-range-thumb]:size-5 [&::-moz-range-thumb]:rounded-full [&::-moz-range-thumb]:border-0 [&::-moz-range-thumb]:bg-[#2563eb] focus-visible:[&::-webkit-slider-thumb]:shadow-[0_0_0_1px_#fff,0_0_0_4px_#2563eb] focus-visible:[&::-moz-range-thumb]:shadow-[0_0_0_1px_#fff,0_0_0_4px_#2563eb]">
</div>
</div>
<div><span class="mb-4 block text-[.72rem] tracking-[.12em] text-[#64748b] uppercase">File drop area</span>
<div class="flex max-w-[360px] flex-col gap-[7px]">
<label for="p3" class="text-[.82rem] font-semibold text-[#334155]">Project files</label>
<div data-drop class="relative rounded-[12px] border-2 border-dashed border-[#8492a6] bg-white p-[30px] text-center transition-[border-color,background-color,box-shadow] duration-[160ms] hover:border-[#2563eb] hover:bg-[#eff6ff] focus-within:border-[#2563eb] focus-within:shadow-[0_0_0_3px_#dbeafe] motion-reduce:transition-none [&.is-over]:border-[#2563eb] [&.is-over]:bg-[#eff6ff]">
<input id="p3" name="files" type="file" multiple accept=".png,.jpg,.jpeg,.pdf" aria-describedby="p3-hint p3-files" class="absolute inset-0 size-full cursor-pointer opacity-0">
<b class="mb-[5px] block text-[.92rem]">Drop files here, or browse</b>
<span id="p3-hint" class="text-[.79rem] text-[#64748b]">PNG, JPG or PDF up to 10MB</span>
<span id="p3-files" class="files mt-2 block text-[.79rem] font-semibold [overflow-wrap:anywhere] text-[#0f172a] empty:hidden" aria-live="polite"></span>
</div>
</div>
</div>
<div><span class="mb-4 block text-[.72rem] tracking-[.12em] text-[#64748b] uppercase">Number stepper</span>
<div class="flex max-w-[360px] flex-col gap-[7px]">
<label for="p4" class="text-[.82rem] font-semibold text-[#334155]">Licence seats</label>
<div class="flex max-w-[170px]" data-stepper>
<button type="button" data-step="-1" aria-controls="p4" aria-label="Remove a seat" class="w-[42px] flex-none cursor-pointer rounded-l-[9px] border border-r-0 border-[#8492a6] bg-[#f8fafc] text-[1.05rem] text-[#334155] hover:bg-[#eef2f7] focus-visible:relative focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#2563eb]">−</button>
<input id="p4" name="seats" type="number" inputmode="numeric" min="1" max="50" value="3" class="w-full min-w-0 [appearance:textfield] border border-[#8492a6] bg-white px-[14px] py-[11px] text-center text-[.92rem] text-[#0f172a] transition-[border-color,box-shadow] duration-[160ms] focus:border-[#2563eb] focus:shadow-[0_0_0_3px_#dbeafe] focus:outline-none motion-reduce:transition-none [&::-webkit-inner-spin-button]:m-0 [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:m-0 [&::-webkit-outer-spin-button]:appearance-none">
<button type="button" data-step="1" aria-controls="p4" aria-label="Add a seat" class="w-[42px] flex-none cursor-pointer rounded-r-[9px] border border-l-0 border-[#8492a6] bg-[#f8fafc] text-[1.05rem] text-[#334155] hover:bg-[#eef2f7] focus-visible:relative focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#2563eb]">+</button>
</div>
<span class="sr-only" aria-live="polite" data-stepper-status></span>
</div>
</div>
JavaScript
// Range: show the value beside the label and give screen readers the same text.
document.querySelectorAll('input[data-range-output]').forEach(function (range) {
  var out = document.getElementById(range.dataset.rangeOutput);
  var update = function () {
    var text = '£' + Number(range.value).toLocaleString('en-GB');
    out.textContent = text; range.setAttribute('aria-valuetext', text);
  };
  range.addEventListener('input', update); update();
});
// File drop: list the chosen files, flag any over 10MB, highlight while dragging over.
document.querySelectorAll('[data-drop]').forEach(function (drop) {
  var input = drop.querySelector('input[type=file]'), list = drop.querySelector('.files');
  ['dragenter', 'dragover'].forEach(function (e) { drop.addEventListener(e, function () { drop.classList.add('is-over'); }); });
  ['dragleave', 'drop'].forEach(function (e) { drop.addEventListener(e, function () { drop.classList.remove('is-over'); }); });
  input.addEventListener('change', function () {
    var tooBig = false;
    list.textContent = Array.prototype.map.call(input.files, function (f) {
      if (f.size > 10485760) { tooBig = true; return f.name + ' (over 10MB)'; }
      return f.name;
    }).join(', ');
    input.setCustomValidity(tooBig ? 'Each file must be 10MB or smaller.' : '');
  });
});
// Stepper: the buttons use the input's own min, max and step, and announce the new value.
document.querySelectorAll('[data-stepper]').forEach(function (step) {
  var input = step.querySelector('input'), status = step.parentElement.querySelector('[data-stepper-status]');
  step.querySelectorAll('button[data-step]').forEach(function (btn) {
    btn.addEventListener('click', function () {
      if (btn.dataset.step > 0) input.stepUp(); else input.stepDown();
      input.dispatchEvent(new Event('input', { bubbles: true }));
      if (status) status.textContent = input.value + (input.value === '1' ? ' seat' : ' seats');
    });
  });
});

About this set

Four fields that are more than a line of text. Search is a pill-shaped type=search input with a magnifier drawn from two pseudo-elements, so no icon font or image is needed. Range is a restyled slider with a blue thumb on a slate track; an output element beside the label shows the current budget and the same text is set as aria-valuetext, so screen readers hear pounds rather than a bare number. File drop area stretches the real file input, made transparent, over a dashed panel, so clicking anywhere opens the file picker and files dropped on the panel land in the input natively; a short script lists the chosen file names and flags any over the 10MB hint. Number stepper pairs a type=number input with minus and plus buttons that call stepUp and stepDown, so min, max and step come from the input itself, and a polite live region announces the new seat count. The Bootstrap version uses .form-control, .form-range and an .input-group with two .btn elements; the drop panel is custom in all three versions because Bootstrap has no drop-zone component.

What’s included

  • Search field with a CSS-drawn magnifier
  • Range slider with a live value readout and aria-valuetext
  • File drop panel that accepts both clicks and dropped files
  • Number stepper that respects the input's min, max and step
  • Bootstrap version uses .form-range and .input-group with .btn

Accessibility

Each field has a visible label. The range exposes its value in words through aria-valuetext, the file input's hint and chosen-file list are linked with aria-describedby, and the stepper buttons are labelled Remove a seat and Add a seat, with the result announced in a live region. The slider thumb shows a blue ring on keyboard focus, the drop panel a ring when its input is focused, and the stepper buttons a 2px outline. Transitions are removed under prefers-reduced-motion.

Customise it

In plain CSS change --f-focus (also the slider thumb), --f-track, --f-border and --f-radius on .f. In Bootstrap the same properties sit on .fx beside --bs-border-color, and the stepper buttons take --bs-btn-* variables; in Tailwind edit the bg-, border- and the [&::-webkit-slider-thumb] classes.