MCP HubMCP Hub
スキル一覧に戻る

conform

majiayu000
更新日 Today
21 閲覧
58
9
58
GitHubで表示
メタdesign

について

Conformは、Web標準を使用したプログレッシブエンハンスメントを実装した、RemixとNext.js向けの型安全なフォーム検証ライブラリです。フレームワークとの統合によるサーバーサイド検証を提供しながら、アクセシブルなクライアントサイド機能を維持します。堅牢な検証、型安全性、JavaScriptなしでの適切な機能低下(グレースフルデグラデーション)が必要なフォームを構築する際にご利用ください。

クイックインストール

Claude Code

推奨
プラグインコマンド推奨
/plugin add https://github.com/majiayu000/claude-skill-registry
Git クローン代替
git clone https://github.com/majiayu000/claude-skill-registry.git ~/.claude/skills/conform

このコマンドをClaude Codeにコピー&ペーストしてスキルをインストールします

ドキュメント

Conform

Type-safe form validation library with progressive enhancement for Remix and Next.js.

Quick Start

npm install @conform-to/react @conform-to/zod zod
import { useForm } from '@conform-to/react';
import { parseWithZod } from '@conform-to/zod';
import { z } from 'zod';

const schema = z.object({
  email: z.string().email(),
  password: z.string().min(8),
});

export default function LoginForm() {
  const [form, fields] = useForm({
    onValidate({ formData }) {
      return parseWithZod(formData, { schema });
    },
  });

  return (
    <form id={form.id} onSubmit={form.onSubmit}>
      <input
        name={fields.email.name}
        defaultValue={fields.email.initialValue}
      />
      <div>{fields.email.errors}</div>

      <input
        name={fields.password.name}
        type="password"
        defaultValue={fields.password.initialValue}
      />
      <div>{fields.password.errors}</div>

      <button>Submit</button>
    </form>
  );
}

Remix Integration

Action & Loader

import { json, redirect } from '@remix-run/node';
import { useActionData } from '@remix-run/react';
import { useForm } from '@conform-to/react';
import { parseWithZod } from '@conform-to/zod';
import { z } from 'zod';

const schema = z.object({
  email: z.string().email('Invalid email'),
  password: z.string().min(8, 'At least 8 characters'),
});

export async function action({ request }: ActionFunctionArgs) {
  const formData = await request.formData();
  const submission = parseWithZod(formData, { schema });

  if (submission.status !== 'success') {
    return json(submission.reply());
  }

  // Process valid data
  await createUser(submission.value);
  return redirect('/dashboard');
}

export default function SignUp() {
  const lastResult = useActionData<typeof action>();

  const [form, fields] = useForm({
    lastResult,
    onValidate({ formData }) {
      return parseWithZod(formData, { schema });
    },
    shouldValidate: 'onBlur',
    shouldRevalidate: 'onInput',
  });

  return (
    <form method="post" id={form.id} onSubmit={form.onSubmit}>
      <label>
        Email
        <input name={fields.email.name} />
        <div>{fields.email.errors}</div>
      </label>

      <label>
        Password
        <input name={fields.password.name} type="password" />
        <div>{fields.password.errors}</div>
      </label>

      <button>Sign Up</button>
    </form>
  );
}

Next.js Integration (App Router)

Server Action

'use server';

import { parseWithZod } from '@conform-to/zod';
import { z } from 'zod';

const schema = z.object({
  email: z.string().email(),
  password: z.string().min(8),
});

export async function login(prevState: unknown, formData: FormData) {
  const submission = parseWithZod(formData, { schema });

  if (submission.status !== 'success') {
    return submission.reply();
  }

  // Authenticate user
  const user = await authenticate(submission.value);

  if (!user) {
    return submission.reply({
      formErrors: ['Invalid credentials'],
    });
  }

  redirect('/dashboard');
}

Client Component

'use client';

import { useFormState } from 'react-dom';
import { useForm } from '@conform-to/react';
import { parseWithZod } from '@conform-to/zod';
import { login } from './actions';

export function LoginForm() {
  const [lastResult, action] = useFormState(login, undefined);

  const [form, fields] = useForm({
    lastResult,
    onValidate({ formData }) {
      return parseWithZod(formData, { schema });
    },
  });

  return (
    <form id={form.id} action={action} onSubmit={form.onSubmit}>
      <input name={fields.email.name} />
      <div>{fields.email.errors}</div>

      <input name={fields.password.name} type="password" />
      <div>{fields.password.errors}</div>

      <button>Login</button>
    </form>
  );
}

Form Configuration

useForm Options

const [form, fields] = useForm({
  // Last server result
  lastResult,

  // Client-side validation
  onValidate({ formData }) {
    return parseWithZod(formData, { schema });
  },

  // When to validate
  shouldValidate: 'onBlur',      // 'onSubmit' | 'onBlur' | 'onInput'
  shouldRevalidate: 'onInput',   // When to re-validate after error

  // Initial values
  defaultValue: {
    email: '',
    password: '',
  },

  // Constraint validation (HTML5)
  constraint: getZodConstraint(schema),
});

Form Props

<form
  id={form.id}
  onSubmit={form.onSubmit}
  noValidate  // Disable browser validation
>
  {/* Form errors */}
  {form.errors && <div>{form.errors}</div>}
</form>

Field Helpers

getInputProps

import { getInputProps } from '@conform-to/react';

<input
  {...getInputProps(fields.email, {
    type: 'email',
    ariaAttributes: true,
  })}
/>

// Generates:
// name, id, defaultValue, aria-invalid, aria-describedby

getTextareaProps

import { getTextareaProps } from '@conform-to/react';

<textarea {...getTextareaProps(fields.message)} />

getSelectProps

import { getSelectProps } from '@conform-to/react';

<select {...getSelectProps(fields.country)}>
  <option value="">Select country</option>
  <option value="us">United States</option>
</select>

Checkbox/Radio

import { getInputProps } from '@conform-to/react';

<input
  {...getInputProps(fields.remember, { type: 'checkbox' })}
/>

<input
  {...getInputProps(fields.plan, { type: 'radio', value: 'free' })}
/>
<input
  {...getInputProps(fields.plan, { type: 'radio', value: 'pro' })}
/>

Nested Objects

const schema = z.object({
  user: z.object({
    name: z.string(),
    email: z.string().email(),
  }),
  address: z.object({
    street: z.string(),
    city: z.string(),
  }),
});

function Form() {
  const [form, fields] = useForm({
    onValidate({ formData }) {
      return parseWithZod(formData, { schema });
    },
  });

  const user = fields.user.getFieldset();
  const address = fields.address.getFieldset();

  return (
    <form>
      <input name={user.name.name} />
      <input name={user.email.name} />

      <input name={address.street.name} />
      <input name={address.city.name} />
    </form>
  );
}

Field Arrays

import { useForm, useFieldList, insert, remove } from '@conform-to/react';

const schema = z.object({
  tasks: z.array(z.object({
    title: z.string(),
    done: z.boolean().default(false),
  })),
});

function TaskForm() {
  const [form, fields] = useForm({
    onValidate({ formData }) {
      return parseWithZod(formData, { schema });
    },
    defaultValue: {
      tasks: [{ title: '', done: false }],
    },
  });

  const tasks = fields.tasks.getFieldList();

  return (
    <form id={form.id} onSubmit={form.onSubmit}>
      {tasks.map((task, index) => {
        const taskFields = task.getFieldset();

        return (
          <div key={task.key}>
            <input name={taskFields.title.name} />
            <input
              type="checkbox"
              name={taskFields.done.name}
              value="true"
            />
            <button
              {...form.remove.getButtonProps({
                name: fields.tasks.name,
                index,
              })}
            >
              Remove
            </button>
          </div>
        );
      })}

      <button
        {...form.insert.getButtonProps({
          name: fields.tasks.name,
          defaultValue: { title: '', done: false },
        })}
      >
        Add Task
      </button>

      <button>Submit</button>
    </form>
  );
}

Intent Buttons

Handle different submit actions:

<form>
  <button name="intent" value="save">
    Save Draft
  </button>
  <button name="intent" value="publish">
    Publish
  </button>
</form>
// In action
export async function action({ request }) {
  const formData = await request.formData();
  const intent = formData.get('intent');

  if (intent === 'save') {
    // Save draft
  } else if (intent === 'publish') {
    // Publish
  }
}

File Uploads

const schema = z.object({
  avatar: z.instanceof(File)
    .refine((file) => file.size < 5_000_000, 'Max 5MB'),
});

function AvatarForm() {
  const [form, fields] = useForm({
    onValidate({ formData }) {
      return parseWithZod(formData, { schema });
    },
  });

  return (
    <form method="post" encType="multipart/form-data">
      <input type="file" name={fields.avatar.name} accept="image/*" />
      <div>{fields.avatar.errors}</div>
    </form>
  );
}

Accessibility

Conform automatically handles accessibility:

// Using getInputProps with ariaAttributes
<input
  {...getInputProps(fields.email, {
    type: 'email',
    ariaAttributes: true,
  })}
/>

// Generates:
// aria-invalid="true" when has errors
// aria-describedby pointing to error element

// Error element
<div id={fields.email.errorId}>{fields.email.errors}</div>

Validation Schemas

With Zod

import { parseWithZod, getZodConstraint } from '@conform-to/zod';

const [form, fields] = useForm({
  constraint: getZodConstraint(schema),
  onValidate({ formData }) {
    return parseWithZod(formData, { schema });
  },
});

With Yup

import { parseWithYup, getYupConstraint } from '@conform-to/yup';

const [form, fields] = useForm({
  constraint: getYupConstraint(schema),
  onValidate({ formData }) {
    return parseWithYup(formData, { schema });
  },
});

With Valibot

import { parseWithValibot, getValibotConstraint } from '@conform-to/valibot';

const [form, fields] = useForm({
  constraint: getValibotConstraint(schema),
  onValidate({ formData }) {
    return parseWithValibot(formData, { schema });
  },
});

Error Handling

Field Errors

{fields.email.errors?.map((error, i) => (
  <div key={i} className="error">{error}</div>
))}

Form Errors

{form.errors?.map((error, i) => (
  <div key={i} className="form-error">{error}</div>
))}

Custom Server Errors

// In action
return submission.reply({
  fieldErrors: {
    email: ['Email already registered'],
  },
  formErrors: ['Something went wrong'],
});

See references/api.md for complete API reference.

GitHub リポジトリ

majiayu000/claude-skill-registry
パス: skills/conform

関連スキル

content-collections

メタ

This skill provides a production-tested setup for Content Collections, a TypeScript-first tool that transforms Markdown/MDX files into type-safe data collections with Zod validation. Use it when building blogs, documentation sites, or content-heavy Vite + React applications to ensure type safety and automatic content validation. It covers everything from Vite plugin configuration and MDX compilation to deployment optimization and schema validation.

スキルを見る

creating-opencode-plugins

メタ

This skill provides the structure and API specifications for creating OpenCode plugins that hook into 25+ event types like commands, files, and LSP operations. It offers implementation patterns for JavaScript/TypeScript modules that intercept and extend the AI assistant's lifecycle. Use it when you need to build event-driven plugins for monitoring, custom handling, or extending OpenCode's capabilities.

スキルを見る

polymarket

メタ

This skill enables developers to build applications with the Polymarket prediction markets platform, including API integration for trading and market data. It also provides real-time data streaming via WebSocket to monitor live trades and market activity. Use it for implementing trading strategies or creating tools that process live market updates.

スキルを見る

cloudflare-turnstile

メタ

This skill provides comprehensive guidance for implementing Cloudflare Turnstile as a CAPTCHA-alternative bot protection system. It covers integration for forms, login pages, API endpoints, and frameworks like React/Next.js/Hono, while handling invisible challenges that maintain user experience. Use it when migrating from reCAPTCHA, debugging error codes, or implementing token validation and E2E tests.

スキルを見る