form agree-form

Agree Form — 利用規約・PP・特商法 同意フォーム

利用規約 + プライバシーポリシー + 特定商取引法に基づく表記の 3 点セット同意 + 動詞 CTA。disabled 禁止のため未同意送信は inline error で誘導。

日本特化無料MITv1.1.0

ライブプレビュー

viewport: responsive

コード · components/blocks/agree-form.tsx

"use client";

import { useState, type FormEvent } from "react";
import Link from "next/link";
import { AlertCircle, ArrowRight } from "lucide-react";

import { Button } from "@/components/ui/button";
import { Checkbox } from "@/components/ui/checkbox";
import { Label } from "@/components/ui/label";

/**
 * Shoji agree-form — Japanese consent / 特商法 acknowledgement block.
 *
 * Surface B (Blocks) contract — design-spec §4.5:
 *   - shadcn primitives only (Button, Checkbox, Label)
 *   - shadcn semantic tokens only (bg-background, text-foreground, border, text-destructive)
 *   - Tailwind default scale + default radius — no Shoji-specific overrides
 *
 * What this block solves (ja_specific = true):
 *   1. 利用規約 / プライバシーポリシー / 特定商取引法に基づく表記 を 1 ブロックで提示
 *      (消費者契約法・電子契約法・特商法の3点セットを Form 単位で満たす雛形)
 *   2. `disabled` でボタンを殺さない — 未同意のまま送信したら inline-error で誘導
 *      (prohibited.md: disabled 禁止 / エラーは 色 + アイコン + テキストの 3 点セット)
 *   3. 同意は単一 checkbox(3つ全部に同意)で十分。粒度を分けたい場合は派生ブロックへ
 *   4. CTA は「動詞 + 目的語」の "申し込みを確定する" を採用(「OK」「同意」NG)
 *   5. リンクはすべて external 想定 — `target="_blank"` + `rel="noreferrer"` で別タブ
 */
export function AgreeForm() {
  const [agreed, setAgreed] = useState(false);
  const [showError, setShowError] = useState(false);
  const [submitted, setSubmitted] = useState(false);

  const onSubmit = (e: FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    if (!agreed) {
      setShowError(true);
      return;
    }
    setShowError(false);
    setSubmitted(true);
  };

  return (
    <section className="bg-background text-foreground">
      <div className="mx-auto w-full max-w-xl px-6 py-16 lg:py-24">
        <header className="mb-10 flex flex-col gap-2">
          <p className="font-mono text-xs uppercase tracking-[0.18em] text-muted-foreground">
            Form · 日本特化
          </p>
          <h2 className="text-2xl font-semibold leading-[1.3] tracking-tight lg:text-3xl">
            お申し込み内容の確認
          </h2>
          <p className="text-sm leading-[1.75] text-muted-foreground">
            以下の規約・表記をご確認のうえ、同意して申し込みを確定してください。
          </p>
        </header>

        <form
          onSubmit={onSubmit}
          noValidate
          className="flex flex-col gap-8 rounded-lg border border-border bg-card p-6 sm:p-8"
        >
          <ul className="flex flex-col gap-2 text-sm leading-[1.75]">
            <li className="flex items-baseline justify-between gap-4 border-b border-border pb-2">
              <span className="text-foreground">利用規約</span>
              <Link
                href="/terms"
                target="_blank"
                rel="noreferrer"
                className="inline-flex items-center gap-1 font-medium text-foreground underline-offset-4 hover:underline"
              >
                内容を見る
                <ArrowRight aria-hidden className="h-3.5 w-3.5" />
              </Link>
            </li>
            <li className="flex items-baseline justify-between gap-4 border-b border-border pb-2">
              <span className="text-foreground">プライバシーポリシー</span>
              <Link
                href="/privacy"
                target="_blank"
                rel="noreferrer"
                className="inline-flex items-center gap-1 font-medium text-foreground underline-offset-4 hover:underline"
              >
                内容を見る
                <ArrowRight aria-hidden className="h-3.5 w-3.5" />
              </Link>
            </li>
            <li className="flex items-baseline justify-between gap-4 pb-1">
              <span className="text-foreground">
                特定商取引法に基づく表記
              </span>
              <Link
                href="/legal/tokushoho"
                target="_blank"
                rel="noreferrer"
                className="inline-flex items-center gap-1 font-medium text-foreground underline-offset-4 hover:underline"
              >
                内容を見る
                <ArrowRight aria-hidden className="h-3.5 w-3.5" />
              </Link>
            </li>
          </ul>

          <div className="flex flex-col gap-2">
            <div className="flex items-start gap-3">
              <Checkbox
                id="agree-form-consent"
                checked={agreed}
                onCheckedChange={(state) => {
                  const next = state === true;
                  setAgreed(next);
                  if (next) setShowError(false);
                }}
                aria-describedby={
                  showError ? "agree-form-consent-error" : undefined
                }
                aria-invalid={showError ? true : undefined}
                className={
                  showError
                    ? "border-destructive data-[state=unchecked]:border-destructive"
                    : undefined
                }
              />
              <Label
                htmlFor="agree-form-consent"
                className="text-sm leading-[1.75] text-foreground"
              >
                上記の利用規約・プライバシーポリシー・特定商取引法に基づく表記に同意します。
              </Label>
            </div>
            {showError ? (
              <p
                id="agree-form-consent-error"
                className="ml-7 flex items-start gap-1.5 text-sm text-destructive"
              >
                <AlertCircle
                  aria-hidden
                  className="mt-0.5 h-3.5 w-3.5 shrink-0"
                />
                <span>同意いただかないと申し込みを確定できません。</span>
              </p>
            ) : null}
          </div>

          <div className="flex flex-col items-stretch gap-3 sm:flex-row sm:items-center sm:justify-end">
            <Button type="button" variant="ghost">
              戻って内容を見直す
            </Button>
            <Button type="submit" size="lg">
              申し込みを確定する
              <ArrowRight aria-hidden className="h-4 w-4" />
            </Button>
          </div>

          {submitted ? (
            <p
              role="status"
              className="rounded-md border border-border bg-muted/40 px-4 py-3 text-sm leading-[1.75] text-foreground"
            >
              ご同意ありがとうございます。続けて確認画面に進みます。
            </p>
          ) : null}
        </form>
      </div>
    </section>
  );
}

AI prompt · MDX

shoji_block: agree-form
---
shoji_block: agree-form
shoji_version: 1.1.0
category: form
license: MIT
dependencies: ["lucide-react", "next"]
shadcn_primitives: ["button", "checkbox", "label"]
ja_specific: true
---

# agree-form

> Shoji block for AI agents. Paste this MDX to your AI; it scaffolds a Japanese
> consent / 特商法 acknowledgement form covering 利用規約 + プライバシーポリシー +
> 特定商取引法に基づく表記.

## Component

```tsx
"use client";

import { useState, type FormEvent } from "react";
import Link from "next/link";
import { AlertCircle, ArrowRight } from "lucide-react";

import { Button } from "@/components/ui/button";
import { Checkbox } from "@/components/ui/checkbox";
import { Label } from "@/components/ui/label";

export function AgreeForm() {
  const [agreed, setAgreed] = useState(false);
  const [showError, setShowError] = useState(false);

  const onSubmit = (e: FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    if (!agreed) {
      setShowError(true);
      return;
    }
    // proceed
  };

  // 利用規約 / プライバシーポリシー / 特商法 へのリンク列 + 同意 checkbox + 動詞 CTA
}
```

## Usage

```tsx
import { AgreeForm } from "@/components/blocks/agree-form";

export default function Page() {
  return <AgreeForm />;
}
```

## Block contract (Two-surfaces — design-spec §4.5)

This is a **Surface B block** — it inherits your shadcn theme.

- shadcn primitives only (`Button`, `Checkbox`, `Label`)
- shadcn semantic tokens only (`bg-background`, `text-foreground`, `border`, `text-destructive`)
- Tailwind default scale + your shadcn radius

## What it solves (ja_specific = true)

1. **3-link consent block**: 利用規約 + プライバシーポリシー + 特定商取引法に基づく表記 are all required for B2C JP services. Most templates only show 2.
2. **No `disabled` button**: prohibited.md bans `disabled` for "submit blocked by missing consent". Instead, the CTA stays active and surfaces an inline error explaining what to fix.
3. **3-point error pattern**: error uses color (`text-destructive`) + icon (`AlertCircle`) + descriptive text. Never color alone.
4. **Verb + object CTA**: "申し込みを確定する" instead of "OK" / "同意" / "送信" — per UX guidelines.
5. **External-link defaults**: each policy link opens in a new tab with `rel="noreferrer"` so users keep their form state.

## Customization hints

- **Per-policy granularity**: split the single checkbox into 3 separate ones if your service requires per-document consent (some 特定商取引法 readings demand it for high-value contracts).
- **Link destinations**: replace `/terms` / `/privacy` / `/legal/tokushoho` with your actual routes, or wire to a CMS-driven policy page.
- **Reset error on type**: the block clears `showError` as soon as the checkbox flips to checked — extend this if your form gains other fields with their own validation.
- **Confirmation modal**: the `submitted` `<p role="status">` is a placeholder. Wire it into your own confirm-step modal or routed `/confirm` page.

## A11y

- Checkbox uses an explicit `<Label htmlFor>` with full sentence, not a placeholder-as-label.
- `aria-describedby` + `aria-invalid` connect the error message to the checkbox for screen readers.
- The success `<p role="status">` is announced as a live region without stealing focus.
- Two CTAs (Ghost + Primary) form a hierarchy — never two Primary side-by-side per UX guidelines.
- Each policy link includes inline icon + text — color is never the only affordance.

## Why this is a Shoji block

- **JP-tuned by default.** The 3-document set (規約 + PP + 特商法) is the floor for any Japanese paid service. Shoji bakes it in so projects can't ship without it.
- **AI agent ready.** This MDX is paste-ready; the prohibited.md rationale is encoded in the JSX so generated code inherits the right defaults.
- **No theming opinions.** Pure shadcn primitives + semantic tokens — drop into any shadcn project.

依存パッケージ · npm

  • lucide-react
  • next