<form action="">
<!-- ---------- input[type="text"] ---------- -->
<div class="form_parent">
<label for="name" class="form_title">名前</label>
<input
type="text"
value=""
name="name"
id="name"
class="form_element"
placeholder="例:えゔぉ わーくす"
autocomplete="name"
required
aria-invalid="false"
/>
<span class="error_message" aria-live="off" aria-hidden="true"></span>
</div>
<!-- ---------- input[type="email"] ---------- -->
<div class="form_parent">
<label for="mail" class="form_title">メールアドレス</label>
<input
type="email"
value=""
name="mail"
id="mail"
class="form_element"
placeholder="例:info@evoworx.co.jp"
autocomplete="email"
pattern="[A-Za-z0-9._+\-']+@[A-Za-z0-9.\-]+\.[A-Za-z]{2,}$"
required
aria-invalid="false"
/>
<span class="error_message" aria-live="off" aria-hidden="true"></span>
</div>
<!-- ---------- input[type="tel"] ---------- -->
<div class="form_parent">
<label for="tel" class="form_title">電話番号</label>
<input
type="tel"
value=""
name="tel"
id="tel"
class="form_element"
placeholder="例:03-6417-9340"
autocomplete="tel"
pattern="\d{2,4}-?\d{2,4}-?\d{3,4}"
required
aria-invalid="false"
/>
<span class="error_message" aria-live="off" aria-hidden="true"></span>
</div>
<!-- ---------- input[type="date"] ---------- -->
<div class="form_parent">
<label for="date" class="form_title">生年月日</label>
<input type="date" value="" name="date" id="date" class="form_element" min="1900-01-01" max="2000-01-01" required aria-invalid="false" />
<span class="error_message" aria-live="off" aria-hidden="true"></span>
</div>
<!-- ---------- input[type="url"] ---------- -->
<div class="form_parent">
<label for="url" class="form_title">好きなサイトのURL</label>
<input
type="url"
value=""
name="url"
id="url"
class="form_element"
placeholder="例:https://evoworx.co.jp"
autocomplete="url"
pattern="https://evoworx.co.jp"
required
aria-invalid="false"
/>
<span class="error_message" aria-live="off" aria-hidden="true"></span>
</div>
<!-- ---------- input[type="checkbox"] ---------- -->
<fieldset class="form_parent">
<legend class="form_title">すきな動物</legend>
<ul>
<li class="flex">
<input type="checkbox" value="いぬ" name="favorite_animal" id="dog" />
<label for="dog">いぬ</label>
</li>
<li class="flex">
<input type="checkbox" value="ねこ" name="favorite_animal" id="cat" />
<label for="cat">ねこ</label>
</li>
<li class="flex">
<input type="checkbox" value="うさぎ" name="favorite_animal" id="rabbit" />
<label for="rabbit">うさぎ</label>
</li>
</ul>
</fieldset>
<!-- ---------- input[type="radio"] ---------- -->
<fieldset class="form_parent">
<legend class="form_title">今日のあさごはん</legend>
<ul>
<li class="flex">
<input type="radio" value="ごはん" name="breakfast" id="rice" class="form_element" required />
<label for="rice">ごはん</label>
</li>
<li class="flex">
<input type="radio" value="パン" name="breakfast" id="bread" class="form_element" required />
<label for="bread">パン</label>
</li>
<li class="flex">
<input type="radio" value="めん" name="breakfast" id="noodles" class="form_element" required />
<label for="noodles">めん</label>
</li>
<li class="flex">
<input type="radio" value="たべていない" name="breakfast" id="not" class="form_element" required />
<label for="not">たべていない</label>
</li>
</ul>
<span class="error_message" aria-live="off" aria-hidden="true"></span>
</fieldset>
<!-- ---------- select ---------- -->
<div class="form_parent">
<label for="select" class="form_title">今日の天気</label>
<select name="select" id="select" class="form_element" required aria-invalid="false">
<option value="" selected disabled> 選択してください</option>
<option value="sunny">晴れ</option>
<option value="cloudy">くもり</option>
<option value="rainy">雨</option>
</select>
<span class="error_message" aria-live="off" aria-hidden="true"></span>
</div>
<!-- ---------- textarea ---------- -->
<div class="form_parent">
<label for="message" class="form_title">今日の一句</label>
<textarea name="message" id="message" class="form_element" maxlength="20" minlength="10" required aria-invalid="false"></textarea>
<span class="error_message" aria-live="off" aria-hidden="true"></span>
</div>
</form>
/* フォーム要素 */
form {
display: grid;
grid-template-columns: minmax(40%, 500px);
justify-content: center;
row-gap: 30px;
margin-inline: 20px;
padding-block: 60px;
font-size: 16px;
}
input,
textarea,
select {
display: block;
}
input:not([type='checkbox']):not([type='radio']) {
width: 100%;
min-height: 50px;
}
select {
min-height: 50px;
}
textarea {
resize: vertical;
min-height: 80px;
}
/* フォーム要素の親要素 */
.form_parent {
display: grid;
row-gap: 8px;
}
/* フォームの項目名 */
.form_title {
display: flex;
flex-direction: row-reverse;
align-items: center;
justify-content: flex-end;
column-gap: 8px;
}
/* required属性がある項目名 */
.form_parent:has(:where(input, textarea, select):required) .form_title {
position: relative;
&::before {
content: '必須';
font-size: 12px;
padding: 4px;
color: #fff;
background-color: #e00000;
}
}
/* エラーメッセージ */
.error_message {
display: block;
color: #e00000;
&[aria-hidden='true'] {
display: none;
}
}
/* ラジオボタンとチェックボックス */
.flex {
display: flex;
align-items: center;
}
const FORM_ELEMENT_CLASS = 'form_element'; // フォーム要素のクラス名
const PARENT_CLASS = 'form_parent'; // フォーム要素の親要素のクラス名
const ERROR_MESSAGE_CLASS = 'error_message'; // エラーメッセージ要素のクラス名
const ERROR_CLASS = 'is_error'; // エラー表示用のクラス名
const REQUIRED_CLASS = 'is_required'; // 必須項目表示用のクラス名
const validationEventTypes = ['blur', 'change']; // バリデーションを行うイベント
// エラー状態を追加する
const addErrorState = (parentElement, errorMessageElement, errorMessage, formElement) => {
parentElement.classList.add(ERROR_CLASS);
errorMessageElement.textContent = errorMessage;
errorMessageElement.setAttribute('aria-live', 'polite');
errorMessageElement.setAttribute('aria-hidden', 'false');
if (formElement.hasAttribute('aria-invalid')) {
formElement.setAttribute('aria-invalid', 'true');
}
};
// エラー状態を削除する
const removeErrorState = (parentElement, errorMessageElement, formElement) => {
parentElement.classList.remove(ERROR_CLASS);
errorMessageElement.textContent = '';
errorMessageElement.removeAttribute('aria-live');
errorMessageElement.setAttribute('aria-hidden', 'true');
if (formElement.hasAttribute('aria-invalid')) {
formElement.setAttribute('aria-invalid', 'false');
}
};
// バリデーションのエラー状態を更新する
const updateErrorState = (element, showError, errorMessage = element.validationMessage) => {
const parentElement = element.closest(`.${PARENT_CLASS}`); // フォーム要素の親要素
const errorMessageElement = parentElement.querySelector(`.${ERROR_MESSAGE_CLASS}`); // エラーメッセージを表示する要素
if (!parentElement || !errorMessageElement) return;
if (showError) {
// エラーの場合
addErrorState(parentElement, errorMessageElement, errorMessage, element);
} else {
// エラーでない場合
removeErrorState(parentElement, errorMessageElement, element);
}
};
// バリデーションのハンドリング
const handleErrorValidation = (element) => {
const validity = element.validity;
const showError = validity.patternMismatch || validity.rangeOverflow || validity.rangeUnderflow || validity.tooLong || validity.tooShort || validity.typeMismatch || validity.valueMissing;
updateErrorState(element, showError);
};
// イベントにエラーハンドリングを登録する
const attachValidationHandlers = (element) => {
validationEventTypes.forEach((eventType) => {
element.addEventListener(eventType, (event) => {
const target = event.target;
handleErrorValidation(target);
});
});
};
(() => {
const formElements = document.querySelectorAll(`.${FORM_ELEMENT_CLASS}`);
formElements?.forEach((element) => {
attachValidationHandlers(element);
});
})();
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.