模写初級 #002|LPのお問い合わせフォームを作る


フォーム付きLPの模写課題とは、入力欄が「見た目」ではなく「部品」として成立しているかを確かめる練習です。この002をやり切ると、<label><input>for / id で結び、type をブラウザの挙動から逆算して選べるようになります。

フォームは、段落や見出しと違って「表示は正しいのに機能していない」という壊れ方をします。だからこの課題の合否は、スクリーンショットではなくブラウザの反応で判定します

項目内容
難易度初級
所要時間目安2〜3時間(実装量から見積もった目安で、計測値ではありません)
使う技術HTML / CSS / CSS Grid / メディアクエリ / フォーム関連属性
模写対象Studio Craft(1枚もののLP+お問い合わせフォーム)

差分チェックツールで効率UPお手本コードと自分のコードを比較して、違いを一目で確認できます。練習前にブックマークしておくと便利です。
Diff Checkerを開く →

この課題で模写するページ

模写サンプルサイト  beginner002 / Studio Craft

ヘッダー(ロゴ+ナビ)/ヒーロー/About/Services/Portfolio/Contact/フッターの7ブロックでできた1ページLPです。ナビの各リンクは #about のようなページ内アンカーで、対応するセクションの id に飛びます。Contactのフォームの中身は次の5つです。

  • お名前 … input type="text"・必須
  • メールアドレス … input type="email"・必須
  • 会社名 … input type="text"・任意
  • お問い合わせ種別 … select(お見積もり依頼/ご相談/その他)
  • お問い合わせ内容 … textarea・必須

段組みはCSS Gridです。サンプルのCSSでは、Servicesのカードが4列 →(1024px以下)2列 →(768px以下)1列、Portfolioが3列 → 2列 → 1列、AboutとContactが2カラム →(768px以下)1カラムでした。ブレークポイントを決めるときの目安にしてください。色やフォントを1pxまで合わせる必要はありません。


ラベルと入力欄をつなぐ(for と id)

<label>for には、同じ文書内にあるフォーム部品の id を書きます。MDNによれば、文書内で最初に見つかった、その id を持つ「ラベル付け可能な要素」が対象になり、対象がラベル付け可能でなければ for は何の効果も持ちません。

<div class="form-group">
  <label for="yourName">お名前</label>
  <input type="text" id="yourName" name="yourName" required>
</div>

この結び付けで得られる効果を、MDNは2点挙げています。

  • ラベルがプログラム上も入力欄と結び付くため、スクリーンリーダーが、利用者がその欄にフォーカスした時点でラベルを読み上げる。何を入力する欄かが画面を見ずに分かる
  • ラベルをクリック/タップすると、ブラウザが結び付いた入力欄へフォーカスを渡す。押せる面積が広がり、タッチ操作の人にとって扱いやすくなる

入力欄の隣にただ文字を置くだけでは関連付けになりません。MDNも「<input> に隣接する平文では不十分」と明記しています。<label><input> を包む書き方もありますが、MDNは互換性の観点から for を使う明示的な関連付けを推奨しています。なお <label> の中に見出しタグやリンクを入れるのは避けてください。見出しは支援技術のナビゲーション手段として使われるため妨げになる、とMDNは説明しています。

出典:MDN「<label>: The Label element」(英語版・2026-08-02取得)


入力欄の type を使い分ける

type は見た目を変える属性ではなく、ブラウザに「この欄で何を受け取るか」を伝える属性です。伝えた内容に応じて、検証・キーボード・部品の形が変わります。

typeブラウザがやること使いどころ
text検証なし。素の1行入力お名前・会社名
email送信前に形式を自動検証。:valid / :invalid が自動で付くメールアドレス
tel形式の検証はしない。モバイルで電話番号向けのキーパッドが出ることがある電話番号
number数値として検証し、上下のスピンボタンが付く増減させる数だけ

いちばん間違えやすいのが type="number" です。MDNは「number は増減させる数値のためのもので、郵便番号やクレジットカード番号のように数字だけで構成されているが数値ではない値には適さない」とし、その場合は type="tel"inputmode="numeric" を使うよう案内しています。実害は「見た目が変」では済みません。Chrome 150 で type="number" の欄に 090-1234-5678 を入れると、JavaScriptから読み取れる値が空文字になりましたtype="tel" なら同じ文字列が保持され、検証も通ります。

type="email" の限界も知っておいてください。MDNは「値がメールアドレスとして正しい形式かを検証するだけで、そのアドレスが実在することは保証しない」と明言しています。実測でも yamadayamada@example..com は弾かれましたが、トップレベルドメインのない yamada@example は有効と判定されました。ブラウザの検証は入力ミスを減らす補助であって、最終確認はサーバー側の仕事です。

出典:MDN「<input type=”number”>」MDN「<input type=”tel”>」MDN「<input type=”email”>」(英語版・2026-08-02取得)


required と placeholder は役割が違う

required制約です。空のまま送信しようとするとブラウザが送信を止め、その欄にエラーを表示します。CSSからは :required で狙えます。MDNは、required を付けたら必須だと目で見て分かる表示を欄の近くに置くよう求めています。

一方 placeholder入力例のヒントで、制約でもラベルでもありません。MDNの「Placeholders are not accessible」節は、次の4点を理由にラベル代わりの使用を否定しています。

  • ラベルではないし、その代用にもならない
  • スクリーンリーダーに読まれない
  • 1文字でも入力した時点で消える。入力後に「何の欄だったか」を確認できない
  • ブラウザの自動翻訳が属性を飛ばすことがあり、翻訳されずに残る場合がある

実測でも確認できます。placeholder だけを付けた欄で input.labels.length を調べると 0<label for> を付けた欄では 1 でした。ラベルが1つも紐づいていない欄は、支援技術から見れば「名前のない入力欄」です。模写サンプルはこの点を正しく作っていて、ラベルには「お名前」「メールアドレス」、placeholder には 山田 太郎 example@email.com書き方の例だけを入れています。

出典:MDN「<input>: The HTML Input element」MDN「required」(英語版・2026-08-02取得)


autocomplete を足すと入力が一気に楽になる

入門記事ではあまり触れられませんが、フォームの完成度を最短で上げるのが autocomplete 属性です。MDNの定義では、その欄をどう自動入力するかをブラウザに伝えるヒントで、値は on / off か、決められたトークンです。

入力欄MDNでの定義
お名前name人物のフルネーム。姓名を分けずに使うのが推奨
メールアドレスemailメールアドレス
会社名organization会社・組織の名称
電話番号tel国番号を含む電話番号全体
<label for="yourEmail">メールアドレス</label>
<input type="email" id="yourEmail" name="yourEmail"
       autocomplete="email" placeholder="example@email.com" required>

これは親切機能というだけではありません。MDNは、正しいトークンを与えることがWCAG 2.2 の達成基準 1.3.5「入力目的の特定」(レベルAA)を満たす、と説明しています。入力欄の目的をプログラムから判別できる状態にする、という基準です。逆に autocomplete="off" は、ワンタイムコードのような例外を除いて避けてください。MDNは多くの利用者が頼っている機能を奪うことになると警告しています。

模写サンプルのHTMLを調べたところ、autocomplete1つも使われていませんでした。ここは模写して終わりにせず、自分の実装で足してください。サンプルより良いものを作る、この課題の一番おいしい部分です。出典:MDN「HTML attribute: autocomplete」(英語版・2026-08-02取得)


進め方

フォームから作り始めると、ほぼ確実に迷子になります。次の順番を守ってください。

ステップ1:HTMLだけで骨組みを作る

CSSを1行も書かずに headersection×4 → footer を並べます。崩れていて構いません。Contactの中に <form> を置き、「ラベル+入力欄」を1組にした小さなまとまり(サンプルでは .form-group)を積みます。この時点で foridnametyperequired を書き切るのがコツです。あとから足そうとすると必ずどこかで抜けます。

ステップ2:大枠のレイアウトから整える

ページ全体の最大幅と中央寄せ、セクションの上下余白、カード群の段組みの順に進めます。細部から入ると、あとで全体幅を変えたときに全部やり直しになります。段組みは grid-template-columns で作り、狭い画面ではメディアクエリで 1fr(=1列)に落とすのが最短です。

ステップ3:最後にフォームUIを整える

フォームの見た目は次の4点でほぼ決まります。模写サンプルの実装値も併記します。

  • 入力欄の内側の余白 … padding: 14px 16px(768px以下では 12px 14px
  • 枠線と角丸 … border: 2px solid #e0e0e0border-radius: 8px
  • ラベルとの間隔 … ラベルを display: block にして margin-bottom: 8px、組ごとに margin-bottom: 20px
  • フォーカス時 … 枠線の色を変え、さらに box-shadow: 0 0 0 4px で外側にリングを出す

サンプルは outline: none で既定のフォーカス表示を消していますが、代わりに枠線の色変更と外側のリングを用意しています。消すなら必ず代わりを置いてください。送信ボタンのサイズも軽視しないように。MDNはWCAG 2.1 達成基準 2.5.5 を引いて、操作要素は最低 44×44 CSSピクセルを推奨しています。


完成条件(ここまでできたら合格)

見た目の印象では判定しません。ブラウザを触って、次の5つがすべて再現できたら合格です。外れたときに疑う場所も1つずつ挙げます。

  1. ラベルの文字をクリックすると、対応する入力欄にカーソルが入る。すべての欄で確認する。入らない欄があれば、forid の綴りが完全一致しているか、同じ id をページ内で使い回していないかを見る
  2. 何も入力せずに送信ボタンを押すと、必須欄でエラーが出て送信されない。止まらなければ、required が付いているか、ボタンが type="submit"<form> の内側にあるかを見る
  3. メール欄に「yamada」とだけ入れて送信すると弾かれる。通ってしまうなら、その欄が type="text" のままになっていないかを見る
  4. ブラウザ幅を320pxまで縮めても横スクロールが出ず、入力欄が親からはみ出さない。はみ出すなら、入力欄に box-sizing: border-box が効いているかを見る
  5. ラベルの付いていない入力欄が0個である。開発者ツールのコンソールで [...document.querySelectorAll('input,textarea,select')].filter(el => el.labels.length === 0).length を実行して 0 が返ればOK。1以上なら、その数だけ結び忘れている

加点は2つ。autocomplete を各欄に付けること、必須欄に目に見える印を出すこと。どちらも模写サンプルより一歩進んだ実装になります。


つまずきポイント

この課題で実際に再現できたものだけを挙げます。

症状原因直し方
入力欄が親の枠からはみ出し、横スクロールが出るwidth: 100%paddingborder が足し算されているbox-sizing: border-box を効かせる
入力欄だけフォントが違う・小さいフォーム部品は親のフォント指定を継承しないfont-family: inheritfont-size: 1rem を明示する
ラベルをクリックしても反応しないforid が食い違う、または id が重複している綴りを揃え、id をページ内で一意にする
電話番号を入れたのに値が空になるtype="number" がハイフン入りの文字列を値として保持しないtype="tel" にする
フォーカスしても見た目が変わらないoutline: none だけ書いて代替表現がないborder-colorbox-shadow で可視化する
セレクトボックスだけ見た目が揃わないOS標準の描画が適用されているappearance: none で既定表示を外し、背景画像で矢印を自作する

上2つは数字で見ると納得しやすいので実測値を置きます。幅300pxの箱に width: 100%padding: 14px 16pxborder: 2px の入力欄を置くと実際の幅は 336px、36pxはみ出しました。このときページ全体の内容幅は365pxで、表示幅320pxに対して横スクロールが出ます。フォントも同様で、body に日本語フォントを指定していても素の <input> の実描画は Arial<textarea>monospace の13.33pxでした(body は16px)。「フォームだけ浮いて見える」の正体はほぼこれです。


次の課題と関連記事

1つ前の課題は 模写初級 #001(HTMLの基本構造とセクション分け) です。骨組みの段階でつまずいた人は、先にこちらを終わらせてから戻ってきてください。クリアできたら次は 模写初級 #003(LPレイアウト) へ進みます。フォームで身につけた「1つのまとまりを部品として組む」感覚が、そのままセクション設計に効いてきます。

他のレベルの課題を見たい場合は 模写コーディング課題の一覧 からどうぞ。初級から上級、Figma模写までまとめてあります。

そして、ここで作ったフォームはまだ送信できません。実際に問い合わせを受け取るには、フォーム専用サービスを使うか、サーバー側の受け取り処理が必要です。選び方は LPのお問い合わせフォームツールの選び方 にまとめてあります。


よくある質問(FAQ)

Q. ラベルと入力欄はどうやって結び付けますか?

<input>id を付け、<label>for に同じ値を書きます。MDNによると、その id を持つ文書内で最初のラベル付け可能な要素が対象になり、結び付いた入力欄はスクリーンリーダーでラベルが読み上げられ、ラベルをクリック/タップするとフォーカスが移ります。<label><input> を包む書き方もありますが、MDNは互換性の理由から for を使う明示的な結び付けを推奨しています。

Q. ラベルを置かず、プレースホルダーだけで済ませてはいけませんか?

いけません。MDNは placeholder を「ラベルではなく、その代用にもならない」と明記し、スクリーンリーダーに読まれないこと、入力を始めた時点で消えること、自動翻訳が属性を飛ばす場合があることを理由に挙げています。実測でも placeholder だけの入力欄は labels.length が0で、ラベルが1つも紐づいていない状態でした。プレースホルダーは「山田 太郎」のような入力例に限って使ってください。

Q. 電話番号の欄に type=”number” を使うとどうなりますか?

入力値が消えます。Chrome 150で検証したところ、type="number" の欄に 090-1234-5678 を入れるとJavaScriptから読める値は空文字になりました。MDNも「number は増減させる数値のためのもので、郵便番号やクレジットカード番号のように数字だけで構成されるが数値ではない値には適さない」とし、type="tel"inputmode="numeric" を使うよう案内しています。電話番号は type="tel" が正解です。

Q. type=”email” を付ければメールアドレスの正しさは保証されますか?

されません。MDNは「値がメールアドレスとして正しい形式かを検証するだけで、そのアドレスが実在することや利用者のものであることは保証しない」と明言しています。実測でも yamadayamada@example..com は弾かれた一方、トップレベルドメインのない yamada@example は有効と判定されました。入力ミスを減らす補助と考え、最終確認はサーバー側で行ってください。

Q. autocomplete 属性は初級のうちから付けるべきですか?

付けたほうが良いです。MDNは、正しいトークンを与えることがWCAG 2.2の達成基準1.3.5「入力目的の特定」(レベルAA)を満たすと説明しており、入力の手間を減らす以上の意味があります。名前は name、メールは email、会社名は organization、電話番号は tel と書くだけです。逆に autocomplete="off" は、多くの利用者が頼っている機能を奪うため避けてください。

Q. フォームから実際にメールが届くところまで作るべきですか?

この課題では不要です。初級002のゴールはマークアップとレイアウトまでで、送信処理は別の技術領域になります。実際に問い合わせを受け取れるようにしたくなったら、フォーム専用サービスを使う方法とサーバー側で受け取る方法があるので、LPのお問い合わせフォームツールの選び方を読んでから選んでください。