【Next.js】ログイン画面のデザインと作り方を Tailwind のコード付きで解説
公開日: 2026年10月8日 · 読了目安 9 分
Next.js と Tailwind CSS でログイン画面を作る手順です。中央カードと左右分割のレイアウト、ラベルと autocomplete の付け方、パスワードの表示切り替え、エラーメッセージ、ソーシャルログインの並べ方、写真の上のガラス風カードとスマホ対応まで、コード付きで解説します。
Next.js のログイン画面を使いやすくデザインする
Next.js でアプリを作り、ログイン画面に入力欄2つとボタンを並べた。動きはするけれど、どこか手作り感が抜けない。
ログイン画面の印象は、飾りよりも細部で決まります。ラベル・入力の補助・エラーの出し方が揃うと、それだけで信頼できる画面に見えます。
この記事では、見た目と使いやすさを Tailwind CSS で整えます。認証の仕組みそのものは扱わず、フォームの送信先は仮の処理にしておきます。
この記事で分かることは次のとおりです。
中央カードと左右分割、2つのレイアウトの選び方
ラベルと autocomplete を正しく付けた入力欄
パスワードの表示切り替えボタンとエラーメッセージ
ソーシャルログインボタンの並べ方
写真の上にガラス風のカードを置くときのコントラストとスマホ対応
ログイン画面のデザインで押さえる3つのこと
ログイン画面は毎日のように開かれるのに、誰もじっくりとは見ない画面です。使う人がしたいのは、早く中に入ることだけです。
迷わせない:入力欄は2つ、主なボタンは1つ。見出しで何の画面かがすぐ分かる
手間を省く:パスワード管理ツールやブラウザの自動入力がきちんと効く
失敗から戻しやすい:エラーの内容と直し方が、フォームのそばに出る
デザインの工夫は、この3つを崩さない範囲で足します。写真の背景やガラスの効果も、文字が読めることが前提です。
Step1:ログイン画面のレイアウトを決める(中央カードと左右分割)
ログイン画面の配置には、大きく2つの型があります。
中央カード:画面の真ん中にフォームのカードを1枚置く。どんなサービスにも合い、スマホでも崩れにくい
左右分割:片側にフォーム、反対側に写真や製品の説明を置く。広い画面でブランドの印象を出しやすい
迷ったら中央カードから始めます。下のコードはページの枠だけで、フォームは Step2 で作ります。
import type { Metadata } from "next";
import { LoginForm } from "./LoginForm";
export const metadata: Metadata = { title: "ログイン" };
export default function LoginPage() {
return (
<main className="flex min-h-dvh items-center justify-center bg-zinc-50 px-4 py-12">
<div className="w-full max-w-sm rounded-2xl border border-zinc-200 bg-white p-8 shadow-sm">
<h1 className="text-2xl font-semibold text-zinc-900">ログイン</h1>
<p className="mt-2 text-sm text-zinc-600">アカウントにログインして続けます。</p>
<LoginForm />
</div>
</main>
);
}左右分割のログイン画面にする場合
広い画面だけ2列にし、スマホでは写真を隠してフォームだけにします。lg: から grid を2列にするだけで作れます。
写真は飾りなので aria-hidden を付けます。背景画像は style で指定します。
<main className="grid min-h-dvh lg:grid-cols-2">
<div className="flex items-center justify-center px-4 py-12">
<div className="w-full max-w-sm">{/* 見出しと LoginForm */}</div>
</div>
<div
aria-hidden="true"
className="hidden bg-zinc-900 bg-cover bg-center lg:block"
style={{ backgroundImage: "url(/login-side.jpg)" }}
/>
</main>Step2:ログイン画面の入力欄を作る(ラベルと autocomplete)
入力欄には、必ず label を付けます。プレースホルダー(欄の中の薄い文字)だけだと、入力を始めた途端に何の欄か分からなくなります。
autocomplete 属性も大切です。どの欄に何を入れるかを、ブラウザやパスワード管理ツールに伝える指定です。
送信中の表示とエラーは、React 19 の useActionState で受け取ります。パスワード欄の部品は Step3 で作ります。
"use client";
import { useActionState } from "react";
import { login, type LoginState } from "./actions";
import { PasswordField } from "./PasswordField";
const initialState: LoginState = {};
export function LoginForm() {
const [state, formAction, pending] = useActionState(login, initialState);
return (
<form action={formAction} className="mt-8 space-y-5">
{state.error ? (
<p role="alert" className="rounded-lg bg-red-50 px-4 py-3 text-sm text-red-700">
{state.error}
</p>
) : null}
<div>
<label htmlFor="email" className="block text-sm font-medium text-zinc-800">
メールアドレス
</label>
<input
id="email"
name="email"
type="email"
autoComplete="username"
required
defaultValue={state.email}
className="mt-2 block min-h-11 w-full rounded-lg border border-zinc-300 px-3 text-base focus:border-zinc-900 focus:outline-none focus:ring-2 focus:ring-zinc-900/20"
/>
</div>
<PasswordField />
<button
type="submit"
disabled={pending}
className="flex min-h-11 w-full items-center justify-center rounded-lg bg-zinc-900 text-sm font-medium text-white hover:bg-zinc-700 disabled:opacity-60"
>
{pending ? "ログインしています…" : "ログイン"}
</button>
</form>
);
}ログインフォームの送信先を用意する
フォームの送信先は、サーバーで動く関数(サーバーアクション)にします。ファイルの先頭に "use server" を書くと、フォームから直接呼べます。
ここでは入力の確認だけをする仮の処理です。実際のログインは、Auth.js(NextAuth.js)などの認証ライブラリの説明に沿って、この関数の中で呼びます。
"use server";
export type LoginState = { error?: string; email?: string };
export async function login(_prev: LoginState, formData: FormData): Promise<LoginState> {
const email = String(formData.get("email") ?? "").trim();
const password = String(formData.get("password") ?? "");
if (!email || !password) {
return { error: "メールアドレスとパスワードを入力してください。", email };
}
// ここで認証ライブラリのログイン処理を呼ぶ(仮の処理)。
// 成功したら next/navigation の redirect("/dashboard") で移動する。
return { error: "メールアドレスかパスワードが正しくありません。", email };
}autocomplete は username と current-password にする
ログインの欄は、メールアドレスで入る場合も autocomplete="username" にします。パスワードの欄は current-password です。
新規登録やパスワード変更の画面では、new-password を使います。保存済みのパスワードを出すか、新しく提案するかをブラウザが判断できます。
メールの欄を type="email" にすると、スマホでは @ の入ったキーボードが出ます。
Step3:ログイン画面にパスワードの表示切り替えを付ける
スマホで長いパスワードを伏せ字のまま打つと、打ち間違いに気づけません。目のアイコンのボタンで、入力した文字を表示できるようにします。
アイコンは lucide-react の Eye と EyeOff を使っています。npm i lucide-react で入れるか、手持ちの SVG に替えてください。
"use client";
import { useState } from "react";
import { Eye, EyeOff } from "lucide-react";
export function PasswordField() {
const [visible, setVisible] = useState(false);
return (
<div>
<label htmlFor="password" className="block text-sm font-medium text-zinc-800">
パスワード
</label>
<div className="relative mt-2">
<input
id="password"
name="password"
type={visible ? "text" : "password"}
autoComplete="current-password"
required
className="block min-h-11 w-full rounded-lg border border-zinc-300 pl-3 pr-12 text-base focus:border-zinc-900 focus:outline-none focus:ring-2 focus:ring-zinc-900/20"
/>
<button
type="button"
onClick={() => setVisible((v) => !v)}
aria-controls="password"
aria-pressed={visible}
className="absolute inset-y-0 right-0 flex w-11 items-center justify-center text-zinc-500 hover:text-zinc-900"
>
<span className="sr-only">パスワードを表示</span>
{visible ? <EyeOff aria-hidden="true" className="size-5" /> : <Eye aria-hidden="true" className="size-5" />}
</button>
</div>
</div>
);
}パスワード表示ボタンのコードのポイント
ボタンは type="button" にする。付け忘れると、押したときにフォームが送信される
読み上げ用の名前は「パスワードを表示」で固定し、押されているかは aria-pressed で伝える
ボタンの幅を 44px にし、欄の右側に余白(pr-12)を取る。文字がアイコンに重ならない
ログイン画面のエラーメッセージの出し方
ログインに失敗したときは、メールアドレスとパスワードのどちらが違うかを書きません。登録済みのアドレスかどうかを、第三者に知られないためです。
「メールアドレスかパスワードが正しくありません」と、まとめて伝えます。Step2 のコードでは role="alert" を付け、表示されたら読み上げられるようにしています。
入力したメールアドレスは消さずに残します。赤い色だけでなく文章でも伝えると、色が見分けにくい人にも届きます。
Step4:ログイン画面のソーシャルログインボタンを並べる
Google や Apple でのログインを置くなら、並べ方を先に決めておきます。
メールとソーシャルの間に「または」の区切り線を入れ、2つの方法があると分かるようにする
利用者が多い方法を上に置き、並び順はサービス内のどの画面でも揃える
ボタンの文言は「Google でログイン」のように、動作まで書く
ソーシャルログインが主なサービスなら、ボタンをフォームの上に置きます。メールが主なら、下のようにフォームの下に置きます。
Google と Apple は、ロゴやボタンの見た目のガイドラインをそれぞれ公開しています。ロゴを使うときは、各社のガイドラインに合わせてください。
export function SocialLogin() {
return (
<div className="mt-8">
<div className="flex items-center gap-3 text-xs text-zinc-500">
<span aria-hidden="true" className="h-px flex-1 bg-zinc-200" />
または
<span aria-hidden="true" className="h-px flex-1 bg-zinc-200" />
</div>
<div className="mt-6 grid gap-3">
{/* 押したときの処理は、使う認証ライブラリの方法に合わせて付ける */}
<button
type="button"
className="flex min-h-11 items-center justify-center gap-3 rounded-lg border border-zinc-300 bg-white text-sm font-medium text-zinc-900 hover:bg-zinc-50"
>
{/* Google のロゴ(ガイドラインに沿った SVG) */}
Google でログイン
</button>
<button
type="button"
className="flex min-h-11 items-center justify-center gap-3 rounded-lg bg-black text-sm font-medium text-white hover:bg-zinc-800"
>
{/* Apple のロゴ */}
Apple でサインイン
</button>
</div>
</div>
);
}Step5:写真の上にガラス風のログイン画面を作る
背景に写真を敷き、すりガラスのようなカードを重ねると、ログイン画面に印象が出ます。ガラス風の見た目は、背景をぼかす backdrop-blur と半透明の白で作ります。
気をつけるのは文字の読みやすさです。写真の明るい部分にカードが重なると、白い文字が読めなくなります。
import { LoginForm } from "./LoginForm";
export default function LoginPage() {
return (
<main
className="relative flex min-h-dvh items-center justify-center bg-zinc-900 bg-cover bg-center px-4 py-12"
style={{ backgroundImage: "url(/login-bg.jpg)" }}
>
{/* 写真を暗くして、白い文字のコントラストを保つ */}
<div aria-hidden="true" className="absolute inset-0 bg-gradient-to-b from-black/50 via-black/30 to-black/60" />
<div className="relative w-full max-w-sm rounded-3xl border border-white/20 bg-black/60 p-8 text-white shadow-2xl supports-[backdrop-filter]:bg-white/10 supports-[backdrop-filter]:backdrop-blur-xl">
<h1 className="text-2xl font-semibold">ログイン</h1>
<p className="mt-2 text-sm text-white/80">アカウントにログインして続けます。</p>
<LoginForm />
</div>
</main>
);
}ガラス風のログイン画面でもコントラストを保つ
本文の文字と背景の明るさの差(コントラスト比)は、4.5:1 以上が目安です。WCAG というアクセシビリティの基準が示している値です。
写真の上に黒の半透明を1枚重ねると、写真を替えても文字が読める状態を保てます。ぼかしに対応していないブラウザ向けに、濃い背景(bg-black/60)を先に指定しています。
LoginForm のラベルや枠線も、白系の色(text-white や border-white/30)に替えます。ラベルは必ず欄の外に出し、薄いプレースホルダーに頼らないようにします。
ガラス風 UI の作り方は https://design-layer.com/ja/articles/nextjs-glassmorphism-ui-guide で詳しく解説しています。
スマホでのログイン画面の調整
スマホでは、カードの左右に 16px ほどの余白を残します。上のコードの px-4 と w-full がその指定です。
入力欄の文字は 16px(text-base)以上にします。iPhone の Safari は、これより小さい文字の欄を押すと画面を拡大してしまいます。
高さは min-h-dvh にします。スマホのアドレスバーが出入りしても、カードが画面からはみ出しにくくなります。
ログイン画面でよくある失敗と公開前チェック
ラベルを付けず、プレースホルダーだけで欄の名前を伝える
autocomplete を付けない、または off にする。パスワード管理ツールが使いにくくなる
パスワード欄への貼り付けを禁止する。管理ツールからのコピーが使えなくなる
表示切り替えボタンを type="button" にし忘れ、押すとフォームが送信される
エラーのたびに入力をすべて消す。メールアドレスは残す
背景の写真が明るすぎて、ガラスのカードの文字が読めない
公開前に、ブラウザにパスワードを保存して自動入力を試します。
キーボードの Tab だけで、上から順にすべての欄とボタンへ移れるかも確かめます。
完成したログイン画面をコピーして使う方法

Step5 のガラス風のログイン画面は、写真選びや透明度の調整に時間がかかります。
そんなときは、完成したログイン画面をブラウザで確かめてから、コードを自分の Next.js に載せる方法もあります。
DesignLayer の「リキッドガラス・ログイン」は、自然風景の写真の上にリキッドガラスのカードを置いたログイン画面です。ユーザー名とパスワード・Remember me・Login ボタン・Google と Apple のログインが揃っています。
見出し・ボタンの文言・背景の写真を差し替えて、アプリや SaaS のログインに置けます。認証の処理は、Step2 と同じく自分のサービスに合わせてつなぎます。
詳細ページの「Cursor 用にコピー」を押すと、統合の指示・ライブプレビューの URL・本体の TSX・npm の依存がまとめてコピーされます。Cursor や Claude に貼って、組み込みを頼めます。
閲覧とライブプレビューは無料で、アカウントもカードも要りません。無料のソースコピーは日本時間で1日3回です。
カタログは https://design-layer.com/ja/components から開けます。
Next.js のログイン画面についてよくある質問
迷ったら中央カードです。どんな画面幅でも崩れにくく、作るのも簡単です。
製品の写真や説明を見せたい場合は、広い画面だけ左右分割にします。
まとめ:Next.js でログイン画面を作る手順
レイアウトは中央カードから始め、写真を見せたいときは広い画面だけ左右分割にする
入力欄には label を付け、autocomplete は username と current-password にする
パスワードの表示切り替えは type="button" と aria-pressed で作る
エラーはどちらが違うかを書かずにまとめて伝え、メールアドレスは残す
ガラス風のカードは、写真に黒の半透明を重ねてコントラストを保つ
まずは Step1 と Step2 のコードで、中央カードのログイン画面を作ってみてください。
ログイン後の画面で使うナビの作り方は https://design-layer.com/ja/articles/nextjs-bottom-tab-bar-guide で解説しています。