Form basic
テキスト・メール・セレクト・テキストエリア・チェックボックスを含むお問い合わせフォーム。
2023年12月以降、すべてのブラウザーで利用可能です。
- Baseline
- BaselineWidely available
- 対応ブラウザ
- Chrome120+Edge120+Firefox117+Safari17.2+
プレビューを読み込み中
<form class="form-basic" action="#" method="post">
<div class="form-basic__field">
<label class="form-basic__label" for="form-basic-name">
お名前
<span class="form-basic__required" aria-hidden="true">必須</span>
</label>
<input
class="form-basic__input"
type="text"
id="form-basic-name"
name="name"
placeholder="山田 太郎"
autocomplete="name"
required
/>
</div>
<div class="form-basic__field">
<label class="form-basic__label" for="form-basic-email">
メールアドレス
<span class="form-basic__required" aria-hidden="true">必須</span>
</label>
<input
class="form-basic__input"
type="email"
id="form-basic-email"
name="email"
placeholder="example@mail.com"
autocomplete="email"
required
/>
</div>
<div class="form-basic__field">
<span class="form-basic__label">お問い合わせ種別</span>
<div class="form-basic__radio-group">
<label class="form-basic__radio">
<input
class="form-basic__radio-input"
type="radio"
name="category"
value="service"
checked
/>
<span class="form-basic__radio-text">サービスについて</span>
</label>
<label class="form-basic__radio">
<input
class="form-basic__radio-input"
type="radio"
name="category"
value="recruit"
/>
<span class="form-basic__radio-text">採用について</span>
</label>
<label class="form-basic__radio">
<input
class="form-basic__radio-input"
type="radio"
name="category"
value="other"
/>
<span class="form-basic__radio-text">その他</span>
</label>
</div>
</div>
<div class="form-basic__field">
<label class="form-basic__label" for="form-basic-subject">件名</label>
<select
class="select-basic js-select-basic"
id="form-basic-subject"
name="subject"
aria-label="件名"
>
<option value="" disabled selected hidden>選択してください</option>
<option value="quote">お見積もり</option>
<option value="support">サポート</option>
<option value="partnership">提携</option>
</select>
</div>
<div class="form-basic__field">
<label class="form-basic__label" for="form-basic-message">
お問い合わせ内容
<span class="form-basic__required" aria-hidden="true">必須</span>
</label>
<textarea
class="form-basic__textarea"
id="form-basic-message"
name="message"
rows="6"
placeholder="お問い合わせ内容をご記入ください"
required
></textarea>
</div>
<div class="form-basic__field">
<label class="form-basic__checkbox">
<input
class="form-basic__checkbox-input"
type="checkbox"
name="privacy"
required
/>
<span class="form-basic__checkbox-text">
<a href="#" class="form-basic__link">プライバシーポリシー</a>に同意する
</span>
</label>
</div>
<div class="form-basic__actions">
<button class="form-basic__submit" type="submit">送信する</button>
</div>
</form>
<form class="form-basic" action="#" method="post">
<div class="form-basic__field">
<label class="form-basic__label" for="form-basic-name">
お名前
<span class="form-basic__required" aria-hidden="true">必須</span>
</label>
<input
class="form-basic__input"
type="text"
id="form-basic-name"
name="name"
placeholder="山田 太郎"
autocomplete="name"
required
/>
</div>
<div class="form-basic__field">
<label class="form-basic__label" for="form-basic-email">
メールアドレス
<span class="form-basic__required" aria-hidden="true">必須</span>
</label>
<input
class="form-basic__input"
type="email"
id="form-basic-email"
name="email"
placeholder="example@mail.com"
autocomplete="email"
required
/>
</div>
<div class="form-basic__field">
<span class="form-basic__label">お問い合わせ種別</span>
<div class="form-basic__radio-group">
<label class="form-basic__radio">
<input
class="form-basic__radio-input"
type="radio"
name="category"
value="service"
checked
/>
<span class="form-basic__radio-text">サービスについて</span>
</label>
<label class="form-basic__radio">
<input
class="form-basic__radio-input"
type="radio"
name="category"
value="recruit"
/>
<span class="form-basic__radio-text">採用について</span>
</label>
<label class="form-basic__radio">
<input
class="form-basic__radio-input"
type="radio"
name="category"
value="other"
/>
<span class="form-basic__radio-text">その他</span>
</label>
</div>
</div>
<div class="form-basic__field">
<label class="form-basic__label" for="form-basic-subject">件名</label>
<select
class="select-basic js-select-basic"
id="form-basic-subject"
name="subject"
aria-label="件名"
>
<option value="" disabled selected hidden>選択してください</option>
<option value="quote">お見積もり</option>
<option value="support">サポート</option>
<option value="partnership">提携</option>
</select>
</div>
<div class="form-basic__field">
<label class="form-basic__label" for="form-basic-message">
お問い合わせ内容
<span class="form-basic__required" aria-hidden="true">必須</span>
</label>
<textarea
class="form-basic__textarea"
id="form-basic-message"
name="message"
rows="6"
placeholder="お問い合わせ内容をご記入ください"
required
></textarea>
</div>
<div class="form-basic__field">
<label class="form-basic__checkbox">
<input
class="form-basic__checkbox-input"
type="checkbox"
name="privacy"
required
/>
<span class="form-basic__checkbox-text">
<a href="#" class="form-basic__link">プライバシーポリシー</a>に同意する
</span>
</label>
</div>
<div class="form-basic__actions">
<button class="form-basic__submit" type="submit">送信する</button>
</div>
</form>
/* reset */
:where(form).form-basic {
margin: 0;
}
:where(label).form-basic__label,
:where(label).form-basic__radio,
:where(label).form-basic__checkbox {
font: inherit;
cursor: pointer;
}
:where(input, select, textarea, button).form-basic__input,
:where(input, select, textarea, button).form-basic__textarea,
:where(input, select, textarea, button).form-basic__radio-input,
:where(input, select, textarea, button).form-basic__checkbox-input,
:where(input, select, textarea, button).form-basic__submit {
box-sizing: border-box;
font: inherit;
appearance: none;
}
:where(button).form-basic__submit {
cursor: pointer;
border: none;
}
:where(a).form-basic__link {
color: inherit;
}
/* snippet */
.form-basic {
--form-border: #e7e7e7;
--form-label-color: #364153;
--form-text-color: #364153;
--form-placeholder-color: #99a1af;
--form-background: #fff;
--form-background-hover: #f7f8fa;
--form-required-color: #fff;
--form-required-background: #e7000b;
--form-accent: #2b36f5;
--form-accent-hover: #1f28a8;
--form-submit-color: #fff;
--form-focus-color: var(--form-accent);
--form-duration: 0.2s;
display: grid;
row-gap: 24px;
width: 100%;
max-width: 640px;
}
.form-basic__field {
display: grid;
row-gap: 8px;
}
.form-basic__label {
display: inline-flex;
column-gap: 8px;
align-items: center;
font-size: 14px;
font-weight: 700;
line-height: 1.5;
color: var(--form-label-color);
}
.form-basic__required {
display: inline-grid;
place-items: center;
padding: 2px 6px;
font-size: 10px;
font-weight: 700;
line-height: 1;
color: var(--form-required-color);
background-color: var(--form-required-background);
border-radius: 4px;
}
.form-basic__input,
.form-basic__textarea {
width: 100%;
padding: 14px 16px;
font-size: 14px;
line-height: 1.6;
color: var(--form-text-color);
background-color: var(--form-background);
border: 1px solid var(--form-border);
border-radius: 8px;
&::placeholder {
color: var(--form-placeholder-color);
}
&:focus:not(:focus-visible) {
outline: none;
}
&:focus-visible {
outline: 2px solid var(--form-focus-color);
outline-offset: 2px;
}
}
.form-basic__textarea {
min-height: 160px;
resize: vertical;
}
.form-basic__radio-group {
display: grid;
row-gap: 12px;
}
.form-basic__radio {
display: inline-flex;
column-gap: 10px;
align-items: center;
font-size: 14px;
line-height: 1.6;
color: var(--form-text-color);
}
.form-basic__radio-input {
flex-shrink: 0;
width: 20px;
height: 20px;
cursor: pointer;
background-color: var(--form-background);
border: 1px solid var(--form-border);
border-radius: calc(infinity * 1px);
&:checked {
background-color: var(--form-accent);
background-image: radial-gradient(
circle,
#fff 0,
#fff 35%,
transparent 36%
);
border-color: var(--form-accent);
}
&:focus:not(:focus-visible) {
outline: none;
}
&:focus-visible {
outline: 2px solid var(--form-focus-color);
outline-offset: 2px;
}
}
.form-basic__checkbox {
display: inline-flex;
column-gap: 10px;
align-items: flex-start;
font-size: 14px;
line-height: 1.6;
color: var(--form-text-color);
}
.form-basic__checkbox-input {
flex-shrink: 0;
width: 20px;
height: 20px;
margin-top: 2px;
cursor: pointer;
background-color: var(--form-background);
border: 1px solid var(--form-border);
border-radius: 4px;
&:checked {
background-color: var(--form-accent);
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12' fill='none'%3E%3Cpath d='M2.5 6l2.5 2.5 4.5-5' stroke='%23fff' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: center;
border-color: var(--form-accent);
}
&:focus:not(:focus-visible) {
outline: none;
}
&:focus-visible {
outline: 2px solid var(--form-focus-color);
outline-offset: 2px;
}
}
.form-basic__link {
text-decoration: underline;
text-underline-offset: 2px;
border-radius: 4px;
@media (any-hover: hover) {
&:hover {
text-decoration: none;
}
}
&:focus:not(:focus-visible) {
outline: none;
}
&:focus-visible {
outline: 2px solid var(--form-focus-color);
outline-offset: 2px;
}
}
.form-basic__actions {
display: flex;
justify-content: center;
padding-top: 8px;
}
.form-basic__submit {
display: inline-grid;
place-items: center;
width: 100%;
max-width: 320px;
padding: 20px 24px;
font-size: 14px;
font-weight: 700;
color: var(--form-submit-color);
background-color: var(--form-accent);
border-radius: calc(infinity * 1px);
@media (any-hover: hover) {
transition: background-color var(--form-duration);
&:hover {
background-color: var(--form-accent-hover);
}
}
&:focus:not(:focus-visible) {
outline: none;
}
&:focus-visible {
outline: 2px solid var(--form-focus-color);
outline-offset: 2px;
}
}
/* reduced motion */
@media (prefers-reduced-motion: reduce) {
.form-basic__submit {
@media (any-hover: hover) {
transition: none;
}
}
}
/* select-basic */
:where(select).select-basic__native {
box-sizing: border-box;
font: inherit;
}
.select-basic {
--select-border: #e7e7e7;
--select-text-color: #364153;
--select-background: #fff;
--select-option-hover: #f3f4f6;
--select-focus-color: #2b36f5;
--select-radius: 8px;
--select-list-max-height: 240px;
--select-arrow: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16' fill='none'%3E%3Cpath d='M4 6l4 4 4-4' stroke='%236a7282' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
position: relative;
width: 100%;
font-size: 14px;
line-height: 1.6;
color: var(--select-text-color);
}
.select-basic__native {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
white-space: nowrap;
border: 0;
clip-path: inset(50%);
}
.select-basic__trigger {
box-sizing: border-box;
display: flex;
align-items: center;
width: 100%;
padding: 14px 40px 14px 16px;
font: inherit;
font-size: inherit;
line-height: inherit;
color: var(--select-text-color);
text-align: start;
cursor: pointer;
background-color: var(--select-background);
background-image: var(--select-arrow);
background-repeat: no-repeat;
background-position: right 16px center;
border: 1px solid var(--select-border);
border-radius: var(--select-radius);
transition: background-color 0.15s ease;
&:focus:not(:focus-visible) {
outline: none;
}
&:focus-visible {
outline: 2px solid var(--select-focus-color);
outline-offset: 2px;
}
}
.select-basic:not([data-open]) .select-basic__trigger:hover {
background-color: var(--select-option-hover);
}
.select-basic__list {
position: absolute;
top: 100%;
left: 0;
z-index: 1;
box-sizing: border-box;
width: 100%;
max-height: var(--select-list-max-height);
padding: 4px;
margin: 4px 0 0;
overflow-y: auto;
list-style: none;
scrollbar-width: none;
background-color: var(--select-background);
border: 1px solid var(--select-border);
border-radius: var(--select-radius);
}
.select-basic__list::-webkit-scrollbar {
display: none;
}
.select-basic__option {
padding: 10px 16px;
color: var(--select-text-color);
cursor: pointer;
border-radius: calc(var(--select-radius) - 2px);
transition: background-color 0.15s ease;
&[aria-selected="true"],
&:hover,
&:focus {
outline: none;
background-color: var(--select-option-hover);
}
}
/* reset */
:where(form).form-basic {
margin: 0;
}
:where(label).form-basic__label,
:where(label).form-basic__radio,
:where(label).form-basic__checkbox {
font: inherit;
cursor: pointer;
}
:where(input, select, textarea, button).form-basic__input,
:where(input, select, textarea, button).form-basic__textarea,
:where(input, select, textarea, button).form-basic__radio-input,
:where(input, select, textarea, button).form-basic__checkbox-input,
:where(input, select, textarea, button).form-basic__submit {
box-sizing: border-box;
font: inherit;
appearance: none;
}
:where(button).form-basic__submit {
cursor: pointer;
border: none;
}
:where(a).form-basic__link {
color: inherit;
}
/* snippet */
.form-basic {
--form-border: #e7e7e7;
--form-label-color: #364153;
--form-text-color: #364153;
--form-placeholder-color: #99a1af;
--form-background: #fff;
--form-background-hover: #f7f8fa;
--form-required-color: #fff;
--form-required-background: #e7000b;
--form-accent: #2b36f5;
--form-accent-hover: #1f28a8;
--form-submit-color: #fff;
--form-focus-color: var(--form-accent);
--form-duration: 0.2s;
display: grid;
row-gap: 24px;
width: 100%;
max-width: 640px;
}
.form-basic__field {
display: grid;
row-gap: 8px;
}
.form-basic__label {
display: inline-flex;
column-gap: 8px;
align-items: center;
font-size: 14px;
font-weight: 700;
line-height: 1.5;
color: var(--form-label-color);
}
.form-basic__required {
display: inline-grid;
place-items: center;
padding: 2px 6px;
font-size: 10px;
font-weight: 700;
line-height: 1;
color: var(--form-required-color);
background-color: var(--form-required-background);
border-radius: 4px;
}
.form-basic__input,
.form-basic__textarea {
width: 100%;
padding: 14px 16px;
font-size: 14px;
line-height: 1.6;
color: var(--form-text-color);
background-color: var(--form-background);
border: 1px solid var(--form-border);
border-radius: 8px;
&::placeholder {
color: var(--form-placeholder-color);
}
&:focus:not(:focus-visible) {
outline: none;
}
&:focus-visible {
outline: 2px solid var(--form-focus-color);
outline-offset: 2px;
}
}
.form-basic__textarea {
min-height: 160px;
resize: vertical;
}
.form-basic__radio-group {
display: grid;
row-gap: 12px;
}
.form-basic__radio {
display: inline-flex;
column-gap: 10px;
align-items: center;
font-size: 14px;
line-height: 1.6;
color: var(--form-text-color);
}
.form-basic__radio-input {
flex-shrink: 0;
width: 20px;
height: 20px;
cursor: pointer;
background-color: var(--form-background);
border: 1px solid var(--form-border);
border-radius: calc(infinity * 1px);
&:checked {
background-color: var(--form-accent);
background-image: radial-gradient(
circle,
#fff 0,
#fff 35%,
transparent 36%
);
border-color: var(--form-accent);
}
&:focus:not(:focus-visible) {
outline: none;
}
&:focus-visible {
outline: 2px solid var(--form-focus-color);
outline-offset: 2px;
}
}
.form-basic__checkbox {
display: inline-flex;
column-gap: 10px;
align-items: flex-start;
font-size: 14px;
line-height: 1.6;
color: var(--form-text-color);
}
.form-basic__checkbox-input {
flex-shrink: 0;
width: 20px;
height: 20px;
margin-top: 2px;
cursor: pointer;
background-color: var(--form-background);
border: 1px solid var(--form-border);
border-radius: 4px;
&:checked {
background-color: var(--form-accent);
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12' fill='none'%3E%3Cpath d='M2.5 6l2.5 2.5 4.5-5' stroke='%23fff' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: center;
border-color: var(--form-accent);
}
&:focus:not(:focus-visible) {
outline: none;
}
&:focus-visible {
outline: 2px solid var(--form-focus-color);
outline-offset: 2px;
}
}
.form-basic__link {
text-decoration: underline;
text-underline-offset: 2px;
border-radius: 4px;
@media (any-hover: hover) {
&:hover {
text-decoration: none;
}
}
&:focus:not(:focus-visible) {
outline: none;
}
&:focus-visible {
outline: 2px solid var(--form-focus-color);
outline-offset: 2px;
}
}
.form-basic__actions {
display: flex;
justify-content: center;
padding-top: 8px;
}
.form-basic__submit {
display: inline-grid;
place-items: center;
width: 100%;
max-width: 320px;
padding: 20px 24px;
font-size: 14px;
font-weight: 700;
color: var(--form-submit-color);
background-color: var(--form-accent);
border-radius: calc(infinity * 1px);
@media (any-hover: hover) {
transition: background-color var(--form-duration);
&:hover {
background-color: var(--form-accent-hover);
}
}
&:focus:not(:focus-visible) {
outline: none;
}
&:focus-visible {
outline: 2px solid var(--form-focus-color);
outline-offset: 2px;
}
}
/* reduced motion */
@media (prefers-reduced-motion: reduce) {
.form-basic__submit {
@media (any-hover: hover) {
transition: none;
}
}
}
/* select-basic */
:where(select).select-basic__native {
box-sizing: border-box;
font: inherit;
}
.select-basic {
--select-border: #e7e7e7;
--select-text-color: #364153;
--select-background: #fff;
--select-option-hover: #f3f4f6;
--select-focus-color: #2b36f5;
--select-radius: 8px;
--select-list-max-height: 240px;
--select-arrow: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16' fill='none'%3E%3Cpath d='M4 6l4 4 4-4' stroke='%236a7282' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
position: relative;
width: 100%;
font-size: 14px;
line-height: 1.6;
color: var(--select-text-color);
}
.select-basic__native {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
white-space: nowrap;
border: 0;
clip-path: inset(50%);
}
.select-basic__trigger {
box-sizing: border-box;
display: flex;
align-items: center;
width: 100%;
padding: 14px 40px 14px 16px;
font: inherit;
font-size: inherit;
line-height: inherit;
color: var(--select-text-color);
text-align: start;
cursor: pointer;
background-color: var(--select-background);
background-image: var(--select-arrow);
background-repeat: no-repeat;
background-position: right 16px center;
border: 1px solid var(--select-border);
border-radius: var(--select-radius);
transition: background-color 0.15s ease;
&:focus:not(:focus-visible) {
outline: none;
}
&:focus-visible {
outline: 2px solid var(--select-focus-color);
outline-offset: 2px;
}
}
.select-basic:not([data-open]) .select-basic__trigger:hover {
background-color: var(--select-option-hover);
}
.select-basic__list {
position: absolute;
top: 100%;
left: 0;
z-index: 1;
box-sizing: border-box;
width: 100%;
max-height: var(--select-list-max-height);
padding: 4px;
margin: 4px 0 0;
overflow-y: auto;
list-style: none;
scrollbar-width: none;
background-color: var(--select-background);
border: 1px solid var(--select-border);
border-radius: var(--select-radius);
}
.select-basic__list::-webkit-scrollbar {
display: none;
}
.select-basic__option {
padding: 10px 16px;
color: var(--select-text-color);
cursor: pointer;
border-radius: calc(var(--select-radius) - 2px);
transition: background-color 0.15s ease;
&[aria-selected="true"],
&:hover,
&:focus {
outline: none;
background-color: var(--select-option-hover);
}
}
(function () {
let instanceIndex = 0;
let documentClickListenerBound = false;
function bindDocumentClickListener() {
if (documentClickListenerBound) return;
documentClickListenerBound = true;
document.addEventListener("click", (event) => {
for (const wrapper of document.querySelectorAll(
'.select-basic[data-open="true"]',
)) {
if (wrapper.contains(event.target)) continue;
const trigger = wrapper.querySelector(".select-basic__trigger");
const list = wrapper.querySelector(".select-basic__list");
if (trigger && list) {
closeList(wrapper, trigger, list);
}
}
});
}
function getSelectableOptions(select) {
return [...select.options].filter((option) => !option.hidden);
}
function updateTriggerText(trigger, select) {
const selected = select.options[select.selectedIndex];
trigger.textContent = selected?.textContent?.trim() ?? "";
}
function syncOptionStates(optionItems, select) {
optionItems.forEach(({ li, option }) => {
li.setAttribute("aria-selected", String(option.selected));
});
}
function setSelectedIndex(select, index) {
select.selectedIndex = index;
select.dispatchEvent(new Event("change", { bubbles: true }));
}
function closeList(wrapper, trigger, list) {
delete wrapper.dataset.open;
trigger.setAttribute("aria-expanded", "false");
list.hidden = true;
}
function openList(wrapper, trigger, list, optionItems) {
wrapper.dataset.open = "true";
trigger.setAttribute("aria-expanded", "true");
list.hidden = false;
const selectedItem =
optionItems.find(({ option }) => option.selected) ?? optionItems[0];
selectedItem?.li.focus();
}
function initSelectBasic(select) {
if (select.dataset.selectBasicInit === "true") return;
select.dataset.selectBasicInit = "true";
instanceIndex += 1;
const wrapper = document.createElement("div");
wrapper.className = "select-basic";
const ariaLabel = select.getAttribute("aria-label");
const listId = `select-basic-list-${instanceIndex}`;
const triggerId = `select-basic-trigger-${instanceIndex}`;
select.parentNode?.insertBefore(wrapper, select);
wrapper.appendChild(select);
select.classList.remove("select-basic");
select.classList.add("select-basic__native");
select.tabIndex = -1;
select.setAttribute("aria-hidden", "true");
const trigger = document.createElement("button");
trigger.type = "button";
trigger.id = triggerId;
trigger.className = "select-basic__trigger";
trigger.setAttribute("aria-haspopup", "listbox");
trigger.setAttribute("aria-expanded", "false");
trigger.setAttribute("aria-controls", listId);
if (ariaLabel) {
trigger.setAttribute("aria-label", ariaLabel);
}
const list = document.createElement("ul");
list.id = listId;
list.className = "select-basic__list";
list.setAttribute("role", "listbox");
if (ariaLabel) {
list.setAttribute("aria-labelledby", triggerId);
}
list.hidden = true;
const optionItems = getSelectableOptions(select).map((option) => {
const li = document.createElement("li");
li.className = "select-basic__option";
li.setAttribute("role", "option");
li.tabIndex = -1;
li.textContent = option.textContent?.trim() ?? "";
li.dataset.index = String(option.index);
li.addEventListener("click", () => {
setSelectedIndex(select, option.index);
updateTriggerText(trigger, select);
syncOptionStates(optionItems, select);
closeList(wrapper, trigger, list);
trigger.focus();
});
list.appendChild(li);
return { li, option };
});
wrapper.append(trigger, list);
updateTriggerText(trigger, select);
syncOptionStates(optionItems, select);
trigger.addEventListener("click", () => {
const isOpen = wrapper.dataset.open === "true";
if (isOpen) {
closeList(wrapper, trigger, list);
return;
}
openList(wrapper, trigger, list, optionItems);
});
trigger.addEventListener("keydown", (event) => {
const isOpen = wrapper.dataset.open === "true";
if (
!isOpen &&
(event.key === "ArrowDown" ||
event.key === "ArrowUp" ||
event.key === "Enter" ||
event.key === " ")
) {
event.preventDefault();
openList(wrapper, trigger, list, optionItems);
return;
}
if (!isOpen) return;
const currentIndex = optionItems.findIndex(
({ li }) => li === document.activeElement,
);
if (event.key === "Escape") {
event.preventDefault();
closeList(wrapper, trigger, list);
trigger.focus();
return;
}
if (event.key === "ArrowDown") {
event.preventDefault();
const next = optionItems[currentIndex + 1] ?? optionItems[0];
next?.li.focus();
return;
}
if (event.key === "ArrowUp") {
event.preventDefault();
const next = optionItems[currentIndex - 1] ?? optionItems.at(-1);
next?.li.focus();
return;
}
if (event.key === "Enter" || event.key === " ") {
event.preventDefault();
const focused = optionItems[currentIndex];
if (!focused) return;
setSelectedIndex(select, focused.option.index);
updateTriggerText(trigger, select);
syncOptionStates(optionItems, select);
closeList(wrapper, trigger, list);
trigger.focus();
}
});
list.addEventListener("keydown", (event) => {
const currentIndex = optionItems.findIndex(
({ li }) => li === document.activeElement,
);
if (event.key === "Escape") {
event.preventDefault();
closeList(wrapper, trigger, list);
trigger.focus();
return;
}
if (event.key === "ArrowDown") {
event.preventDefault();
const next = optionItems[currentIndex + 1] ?? optionItems[0];
next?.li.focus();
return;
}
if (event.key === "ArrowUp") {
event.preventDefault();
const next = optionItems[currentIndex - 1] ?? optionItems.at(-1);
next?.li.focus();
return;
}
if (event.key === "Enter" || event.key === " ") {
event.preventDefault();
const focused = optionItems[currentIndex];
if (!focused) return;
setSelectedIndex(select, focused.option.index);
updateTriggerText(trigger, select);
syncOptionStates(optionItems, select);
closeList(wrapper, trigger, list);
trigger.focus();
}
});
select.addEventListener("change", () => {
updateTriggerText(trigger, select);
syncOptionStates(optionItems, select);
});
}
function initAll() {
bindDocumentClickListener();
document.querySelectorAll(".js-select-basic").forEach(initSelectBasic);
}
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", initAll);
} else {
initAll();
}
})();
(function () {
let instanceIndex = 0;
let documentClickListenerBound = false;
function bindDocumentClickListener() {
if (documentClickListenerBound) return;
documentClickListenerBound = true;
document.addEventListener("click", (event) => {
for (const wrapper of document.querySelectorAll(
'.select-basic[data-open="true"]',
)) {
if (wrapper.contains(event.target)) continue;
const trigger = wrapper.querySelector(".select-basic__trigger");
const list = wrapper.querySelector(".select-basic__list");
if (trigger && list) {
closeList(wrapper, trigger, list);
}
}
});
}
function getSelectableOptions(select) {
return [...select.options].filter((option) => !option.hidden);
}
function updateTriggerText(trigger, select) {
const selected = select.options[select.selectedIndex];
trigger.textContent = selected?.textContent?.trim() ?? "";
}
function syncOptionStates(optionItems, select) {
optionItems.forEach(({ li, option }) => {
li.setAttribute("aria-selected", String(option.selected));
});
}
function setSelectedIndex(select, index) {
select.selectedIndex = index;
select.dispatchEvent(new Event("change", { bubbles: true }));
}
function closeList(wrapper, trigger, list) {
delete wrapper.dataset.open;
trigger.setAttribute("aria-expanded", "false");
list.hidden = true;
}
function openList(wrapper, trigger, list, optionItems) {
wrapper.dataset.open = "true";
trigger.setAttribute("aria-expanded", "true");
list.hidden = false;
const selectedItem =
optionItems.find(({ option }) => option.selected) ?? optionItems[0];
selectedItem?.li.focus();
}
function initSelectBasic(select) {
if (select.dataset.selectBasicInit === "true") return;
select.dataset.selectBasicInit = "true";
instanceIndex += 1;
const wrapper = document.createElement("div");
wrapper.className = "select-basic";
const ariaLabel = select.getAttribute("aria-label");
const listId = `select-basic-list-${instanceIndex}`;
const triggerId = `select-basic-trigger-${instanceIndex}`;
select.parentNode?.insertBefore(wrapper, select);
wrapper.appendChild(select);
select.classList.remove("select-basic");
select.classList.add("select-basic__native");
select.tabIndex = -1;
select.setAttribute("aria-hidden", "true");
const trigger = document.createElement("button");
trigger.type = "button";
trigger.id = triggerId;
trigger.className = "select-basic__trigger";
trigger.setAttribute("aria-haspopup", "listbox");
trigger.setAttribute("aria-expanded", "false");
trigger.setAttribute("aria-controls", listId);
if (ariaLabel) {
trigger.setAttribute("aria-label", ariaLabel);
}
const list = document.createElement("ul");
list.id = listId;
list.className = "select-basic__list";
list.setAttribute("role", "listbox");
if (ariaLabel) {
list.setAttribute("aria-labelledby", triggerId);
}
list.hidden = true;
const optionItems = getSelectableOptions(select).map((option) => {
const li = document.createElement("li");
li.className = "select-basic__option";
li.setAttribute("role", "option");
li.tabIndex = -1;
li.textContent = option.textContent?.trim() ?? "";
li.dataset.index = String(option.index);
li.addEventListener("click", () => {
setSelectedIndex(select, option.index);
updateTriggerText(trigger, select);
syncOptionStates(optionItems, select);
closeList(wrapper, trigger, list);
trigger.focus();
});
list.appendChild(li);
return { li, option };
});
wrapper.append(trigger, list);
updateTriggerText(trigger, select);
syncOptionStates(optionItems, select);
trigger.addEventListener("click", () => {
const isOpen = wrapper.dataset.open === "true";
if (isOpen) {
closeList(wrapper, trigger, list);
return;
}
openList(wrapper, trigger, list, optionItems);
});
trigger.addEventListener("keydown", (event) => {
const isOpen = wrapper.dataset.open === "true";
if (
!isOpen &&
(event.key === "ArrowDown" ||
event.key === "ArrowUp" ||
event.key === "Enter" ||
event.key === " ")
) {
event.preventDefault();
openList(wrapper, trigger, list, optionItems);
return;
}
if (!isOpen) return;
const currentIndex = optionItems.findIndex(
({ li }) => li === document.activeElement,
);
if (event.key === "Escape") {
event.preventDefault();
closeList(wrapper, trigger, list);
trigger.focus();
return;
}
if (event.key === "ArrowDown") {
event.preventDefault();
const next = optionItems[currentIndex + 1] ?? optionItems[0];
next?.li.focus();
return;
}
if (event.key === "ArrowUp") {
event.preventDefault();
const next = optionItems[currentIndex - 1] ?? optionItems.at(-1);
next?.li.focus();
return;
}
if (event.key === "Enter" || event.key === " ") {
event.preventDefault();
const focused = optionItems[currentIndex];
if (!focused) return;
setSelectedIndex(select, focused.option.index);
updateTriggerText(trigger, select);
syncOptionStates(optionItems, select);
closeList(wrapper, trigger, list);
trigger.focus();
}
});
list.addEventListener("keydown", (event) => {
const currentIndex = optionItems.findIndex(
({ li }) => li === document.activeElement,
);
if (event.key === "Escape") {
event.preventDefault();
closeList(wrapper, trigger, list);
trigger.focus();
return;
}
if (event.key === "ArrowDown") {
event.preventDefault();
const next = optionItems[currentIndex + 1] ?? optionItems[0];
next?.li.focus();
return;
}
if (event.key === "ArrowUp") {
event.preventDefault();
const next = optionItems[currentIndex - 1] ?? optionItems.at(-1);
next?.li.focus();
return;
}
if (event.key === "Enter" || event.key === " ") {
event.preventDefault();
const focused = optionItems[currentIndex];
if (!focused) return;
setSelectedIndex(select, focused.option.index);
updateTriggerText(trigger, select);
syncOptionStates(optionItems, select);
closeList(wrapper, trigger, list);
trigger.focus();
}
});
select.addEventListener("change", () => {
updateTriggerText(trigger, select);
syncOptionStates(optionItems, select);
});
}
function initAll() {
bindDocumentClickListener();
document.querySelectorAll(".js-select-basic").forEach(initSelectBasic);
}
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", initAll);
} else {
initAll();
}
})();