Token导航 LogoToken导航TokenDH.com
研究检索操作浏览器github未标认证来源可访问clear审计通过

form-vanilla香草形式

Agent Skill

form-vanilla 用于查找、检索和筛选相关信息,适合在 Codex、Claude、Cursor、Gemini CLI 中需要根据关键词、任务场景或来源线索快速定位候选结果时使用。可结合来源仓库、安装命令和原始 README 继续核验具体用法。安装前建议确认权限范围、维护状态,以及是否会触发联网、命令执行或文件读写。

总安装

1,035

周安装

44

GitHub Stars

8

下载量

363
CodexClaudeCursorGemini CLI

安装说明

本站只整理中文说明和来源信息,不托管安装包,也不代用户安装。

GitHub

来源数

3

许可证

MIT

最后核验

2026-05-01

来源状态

来源可访问

安装方式

通过对话安装

复制提示词发给支持本地命令或 Skills 的 AI 助手,先确认命令和权限,再让它执行。

请帮我安装这个 Agent Skill:form-vanilla(香草形式)
来源仓库:https://github.com/bbeierle12/skill-mcp-claude
仓库路径:skills/form-vanilla
安装命令:
npx skills add https://github.com/bbeierle12/skill-mcp-claude --skill form-vanilla
安装前请先检查当前环境是否支持对应 CLI,并向我确认将要执行的命令、安装目录、联网范围和文件读写权限;确认后再执行。

命令行安装

复制命令到本机终端执行。不同来源提供的安装方式可能略有差异;本站展示可直接复制的安装命令,安装前请核对来源页面。

skills.shnpx skills
npx skills add https://github.com/bbeierle12/skill-mcp-claude --skill form-vanilla

简介

用于查找、检索和筛选相关信息。form-vanilla 属于研究检索类 Skill,可作为该场景下的辅助能力补充。

  • 适合在关键词搜索或任务场景中快速定位候选结果。
  • 可结合来源仓库 README 核验具体用法。
  • 安装前需确认权限范围和维护状态。适用宿主包括 Codex、Claude、Cursor、Gemini CLI,接入前应确认版本、权限和运行环境要求。
  • 注意是否会触发联网、命令执行或文件读写操作。

SKILL.md

Form Vanilla

Framework-free form patterns using native browser APIs enhanced with Zod.

Quick Start

<form id="login-form" novalidate>
  <div class="form-field">
    <label for="email">Email</label>
    <input
      id="email"
      name="email"
      type="email"
      autocomplete="email"
      required
    />
    <span class="error" aria-live="polite"></span>
  </div>

  <div class="form-field">
    <label for="password">Password</label>
    <input
      id="password"
      name="password"
      type="password"
      autocomplete="current-password"
      required
      minlength="8"
    />
    <span class="error" aria-live="polite"></span>
  </div>

  <button type="submit">Sign in</button>
</form>

<script type="module">
import { createFormValidator } from './vanilla-validator.js';
import { loginSchema } from './schemas.js';

const form = document.getElementById('login-form');
const validator = createFormValidator(form, loginSchema);

form.addEventListener('submit', async (e) => {
  e.preventDefault();

  const result = await validator.validate();
  if (result.valid) {
    console.log('Submit:', result.data);
  }
});
</script>

HTML5 Constraint Validation API

Built-in Attributes

<!-- Required field -->
<input required />

<!-- Length constraints -->
<input minlength="3" maxlength="50" />

<!-- Number constraints -->
<input type="number" min="0" max="100" step="1" />

<!-- Pattern (regex) -->
<input pattern="[A-Za-z]{3}" title="Three letter code" />

<!-- Email validation -->
<input type="email" />

<!-- URL validation -->
<input type="url" />

Validity State Properties

const input = document.querySelector('input');

// Check individual constraints
input.validity.valueMissing;    // required but empty
input.validity.typeMismatch;    // email/url format wrong
input.validity.patternMismatch; // regex failed
input.validity.tooShort;        // < minlength
input.validity.tooLong;         // > maxlength
input.validity.rangeUnderflow;  // < min
input.validity.rangeOverflow;   // > max
input.validity.stepMismatch;    // not divisible by step
input.validity.badInput;        // browser can't parse
input.validity.customError;     // setCustomValidity called

// Check overall validity
input.validity.valid;           // all constraints pass
input.checkValidity();          // returns boolean
input.reportValidity();         // shows browser UI

Custom Error Messages

const input = document.querySelector('#email');

// Set custom validation message
input.addEventListener('invalid', (e) => {
  if (input.validity.valueMissing) {
    input.setCustomValidity('Please enter your email address');
  } else if (input.validity.typeMismatch) {
    input.setCustomValidity('Please enter a valid email (e.g., name@example.com)');
  }
});

// Clear custom message on input
input.addEventListener('input', () => {
  input.setCustomValidity('');
});

Zod Integration

Vanilla Validator Class

// vanilla-validator.ts
import { z } from 'zod';

export interface ValidationResult<T> {
  valid: boolean;
  data?: T;
  errors: Record<string, string>;
}

export interface ValidatorOptions {
  /** When to validate */
  validateOn: 'blur' | 'input' | 'submit';

  /** When to re-validate after error */
  revalidateOn: 'input' | 'blur';

  /** Debounce delay for input validation (ms) */
  debounceMs?: number;
}

const defaultOptions: ValidatorOptions = {
  validateOn: 'blur',
  revalidateOn: 'input',
  debounceMs: 300
};

export function createFormValidator<T extends z.ZodType>(
  form: HTMLFormElement,
  schema: T,
  options: Partial<ValidatorOptions> = {}
): FormValidator<z.infer<T>> {
  const opts = { ...defaultOptions, ...options };
  const fieldErrors = new Map<string, string>();
  const touchedFields = new Set<string>();
  let debounceTimers = new Map<string, ReturnType<typeof setTimeout>>();

  // Get all form fields
  const fields = Array.from(form.elements).filter(
    (el): el is HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement =>
      el instanceof HTMLInputElement ||
      el instanceof HTMLSelectElement ||
      el instanceof HTMLTextAreaElement
  );

  // Attach event listeners
  fields.forEach(field => {
    if (!field.name) return;

    // Blur handler (punish late)
    field.addEventListener('blur', () => {
      touchedFields.add(field.name);
      if (opts.validateOn === 'blur') {
        validateField(field.name);
      }
    });

    // Input handler (real-time correction)
    field.addEventListener('input', () => {
      // Clear existing timer
      const timer = debounceTimers.get(field.name);
      if (timer) clearTimeout(timer);

      // Only validate if already has error (correction mode)
      if (fieldErrors.has(field.name) && opts.revalidateOn === 'input') {
        debounceTimers.set(
          field.name,
          setTimeout(() => validateField(field.name), opts.debounceMs)
        );
      }
    });
  });

  function getFormData(): Record<string, unknown> {
    const data: Record<string, unknown> = {};
    const formData = new FormData(form);

    formData.forEach((value, key) => {
      // Handle checkboxes
      const field = form.elements.namedItem(key);
      if (field instanceof HTMLInputElement && field.type === 'checkbox') {
        data[key] = field.checked;
      } else if (field instanceof HTMLInputElement && field.type === 'number') {
        data[key] = value === '' ? undefined : Number(value);
      } else {
        data[key] = value;
      }
    });

    return data;
  }

  function validateField(name: string): string | undefined {
    const data = getFormData();
    const result = schema.safeParse(data);

    if (result.success) {
      clearFieldError(name);
      return undefined;
    }

    const fieldError = result.error.errors.find(e => e.path[0] === name);
    if (fieldError) {
      setFieldError(name, fieldError.message);
      return fieldError.message;
    } else {
      clearFieldError(name);
      return undefined;
    }
  }

  function setFieldError(name: string, message: string): void {
    fieldErrors.set(name, message);

    const field = form.elements.namedItem(name) as HTMLInputElement | null;
    if (!field) return;

    // Set ARIA attributes
    field.setAttribute('aria-invalid', 'true');

    // Find error element
    const fieldWrapper = field.closest('.form-field');
    const errorEl = fieldWrapper?.querySelector('.error');
    if (errorEl) {
      errorEl.textContent = message;
      field.setAttribute('aria-describedby', errorEl.id || '');
    }

    // Add error class
    fieldWrapper?.classList.add('has-error');
    fieldWrapper?.classList.remove('is-valid');

    // Set custom validity for native UI
    field.setCustomValidity(message);
  }

  function clearFieldError(name: string): void {
    fieldErrors.delete(name);

    const field = form.elements.namedItem(name) as HTMLInputElement | null;
    if (!field) return;

    // Clear ARIA
    field.setAttribute('aria-invalid', 'false');
    field.removeAttribute('aria-describedby');

    // Clear error element
    const fieldWrapper = field.closest('.form-field');
    const errorEl = fieldWrapper?.querySelector('.error');
    if (errorEl) {
      errorEl.textContent = '';
    }

    // Update classes
    fieldWrapper?.classList.remove('has-error');
    if (touchedFields.has(name)) {
      fieldWrapper?.classList.add('is-valid');
    }

    // Clear custom validity
    field.setCustomValidity('');
  }

  function clearAllErrors(): void {
    fieldErrors.forEach((_, name) => clearFieldError(name));
  }

  async function validate(): Promise<ValidationResult<z.infer<T>>> {
    const data = getFormData();
    const result = schema.safeParse(data);

    if (result.success) {
      clearAllErrors();
      return { valid: true, data: result.data, errors: {} };
    }

    // Set errors for all fields
    const errors: Record<string, string> = {};
    result.error.errors.forEach(err => {
      const name = String(err.path[0]);
      errors[name] = err.message;
      setFieldError(name, err.message);
    });

    // Focus first error
    const firstErrorName = Object.keys(errors)[0];
    if (firstErrorName) {
      const field = form.elements.namedItem(firstErrorName) as HTMLElement;
      field?.focus();
    }

    return { valid: false, errors };
  }

  function reset(): void {
    form.reset();
    clearAllErrors();
    touchedFields.clear();
    debounceTimers.forEach(timer => clearTimeout(timer));
    debounceTimers.clear();
  }

  return {
    validate,
    validateField,
    setFieldError,
    clearFieldError,
    clearAllErrors,
    reset,
    getFormData
  };
}

export interface FormValidator<T> {
  validate(): Promise<ValidationResult<T>>;
  validateField(name: string): string | undefined;
  setFieldError(name: string, message: string): void;
  clearFieldError(name: string): void;
  clearAllErrors(): void;
  reset(): void;
  getFormData(): Record<string, unknown>;
}

Usage Example

<!DOCTYPE html>
<html>
<head>
  <style>
    .form-field {
      margin-bottom: 1rem;
    }

    .form-field label {
      display: block;
      margin-bottom: 0.25rem;
    }

    .form-field input {
      width: 100%;
      padding: 0.5rem;
      border: 1px solid #ccc;
      border-radius: 4px;
    }

    .form-field.has-error input {
      border-color: #dc2626;
    }

    .form-field.is-valid input {
      border-color: #059669;
    }

    .form-field .error {
      color: #dc2626;
      font-size: 0.875rem;
      margin-top: 0.25rem;
    }
  </style>
</head>
<body>
  <form id="contact-form" novalidate>
    <div class="form-field">
      <label for="name">Name</label>
      <input id="name" name="name" type="text" autocomplete="name" />
      <span class="error" id="name-error" aria-live="polite"></span>
    </div>

    <div class="form-field">
      <label for="email">Email</label>
      <input id="email" name="email" type="email" autocomplete="email" />
      <span class="error" id="email-error" aria-live="polite"></span>
    </div>

    <div class="form-field">
      <label for="message">Message</label>
      <textarea id="message" name="message" rows="4"></textarea>
      <span class="error" id="message-error" aria-live="polite"></span>
    </div>

    <button type="submit">Send</button>
  </form>

  <script type="module">
    import { z } from 'https://cdn.jsdelivr.net/npm/zod@3/+esm';
    import { createFormValidator } from './vanilla-validator.js';

    const schema = z.object({
      name: z.string().min(1, 'Please enter your name'),
      email: z.string().email('Please enter a valid email'),
      message: z.string().min(10, 'Message must be at least 10 characters')
    });

    const form = document.getElementById('contact-form');
    const validator = createFormValidator(form, schema);

    form.addEventListener('submit', async (e) => {
      e.preventDefault();

      const result = await validator.validate();
      if (result.valid) {
        console.log('Submitting:', result.data);
        // Send to server...
        alert('Message sent!');
        validator.reset();
      }
    });
  </script>
</body>
</html>

Progressive Enhancement

Base HTML (Works Without JS)

<form action="/submit" method="POST">
  <div class="form-field">
    <label for="email">Email *</label>
    <input
      id="email"
      name="email"
      type="email"
      required
      autocomplete="email"
    />
  </div>

  <div class="form-field">
    <label for="password">Password *</label>
    <input
      id="password"
      name="password"
      type="password"
      required
      minlength="8"
      autocomplete="current-password"
    />
  </div>

  <button type="submit">Sign in</button>
</form>

Enhanced With JS

// Only runs if JS is available
const form = document.querySelector('form');

if (form) {
  // Disable native validation UI
  form.setAttribute('novalidate', '');

  // Add ARIA live regions for errors
  form.querySelectorAll('.form-field').forEach(field => {
    const input = field.querySelector('input');
    if (input && input.name) {
      const errorEl = document.createElement('span');
      errorEl.className = 'error';
      errorEl.id = `${input.name}-error`;
      errorEl.setAttribute('aria-live', 'polite');
      field.appendChild(errorEl);
    }
  });

  // Attach validator
  const validator = createFormValidator(form, schema);

  form.addEventListener('submit', async (e) => {
    e.preventDefault();
    const result = await validator.validate();
    if (result.valid) {
      form.submit(); // Native submit
    }
  });
}

Common Patterns

Password Visibility Toggle

<div class="form-field password-field">
  <label for="password">Password</label>
  <div class="input-wrapper">
    <input
      id="password"
      name="password"
      type="password"
      autocomplete="current-password"
    />
    <button
      type="button"
      class="toggle-password"
      aria-label="Show password"
    >
      👁
    </button>
  </div>
</div>

<script>
document.querySelectorAll('.toggle-password').forEach(btn => {
  btn.addEventListener('click', () => {
    const input = btn.previousElementSibling;
    const isPassword = input.type === 'password';

    input.type = isPassword ? 'text' : 'password';
    btn.setAttribute('aria-label', isPassword ? 'Hide password' : 'Show password');
    btn.textContent = isPassword ? '🙈' : '👁';
  });
});
</script>

Character Counter

<div class="form-field">
  <label for="bio">Bio</label>
  <textarea id="bio" name="bio" maxlength="500"></textarea>
  <span class="char-count"><span id="bio-count">0</span>/500</span>
</div>

<script>
const textarea = document.getElementById('bio');
const counter = document.getElementById('bio-count');

textarea.addEventListener('input', () => {
  counter.textContent = textarea.value.length;
});
</script>

Form Submission with Fetch

const form = document.getElementById('my-form');
const submitBtn = form.querySelector('button[type="submit"]');

form.addEventListener('submit', async (e) => {
  e.preventDefault();

  const result = await validator.validate();
  if (!result.valid) return;

  // Disable button
  submitBtn.disabled = true;
  submitBtn.textContent = 'Sending...';

  try {
    const response = await fetch(form.action, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'X-CSRF-Token': document.querySelector('[name="_csrf"]').value
      },
      body: JSON.stringify(result.data)
    });

    if (!response.ok) {
      const error = await response.json();
      // Handle server errors
      if (error.field) {
        validator.setFieldError(error.field, error.message);
      } else {
        alert(error.message);
      }
      return;
    }

    // Success
    alert('Form submitted!');
    validator.reset();

  } catch (err) {
    alert('Network error. Please try again.');
  } finally {
    submitBtn.disabled = false;
    submitBtn.textContent = 'Submit';
  }
});

File Structure

form-vanilla/
├── SKILL.md
├── references/
│   └── constraint-validation.md  # HTML5 Constraint API reference
└── scripts/
    ├── vanilla-validator.ts      # Main validator class
    ├── vanilla-validator.js      # Compiled JS
    ├── progressive-enhance.js    # Progressive enhancement utils
    └── examples/
        ├── login-form.html
        ├── contact-form.html
        └── checkout-form.html

Reference

  • references/constraint-validation.md — HTML5 Constraint Validation API reference

适合场景

01

用户想查找某类 Agent Skill 时

02

需要根据任务场景推荐可安装能力包时

03

需要对比不同来源的安装命令和来源信息时

04

需要参考平台分布和安装热度时

能力概览

能力 1

按任务关键词查找相关 Skills

能力 2

展示可复制的安装命令

能力 3

保留来源站点、仓库和原始说明,方便继续核验

能力 4

补充不同宿主或平台的使用分布数据

能力 5

展示第三方安全扫描或审计结果

安装后应在对应宿主中按原始 README 的触发条件使用;具体调用方式请以来源页面和 README 为准。

平台分布

Claude Code

26.82%
按下载量换算97

OpenCode

26.16%
按下载量换算95

windsurf

16.62%
按下载量换算60

Antigravity

13.45%
按下载量换算49

Gemini CLI

7.75%
按下载量换算28

Codex

3.82%
按下载量换算14

安全审计

Gen Agent Trust Hub

通过

Socket

通过

Snyk

通过

权限和风险

操作浏览器

该 Skill 可能涉及浏览器控制能力,使用时可能读取或操作网页内容,需要在受控环境中确认权限边界。

安装前确认

本站仅展示第三方公开信息,不托管安装包,不提供自动安装或运行环境。安装前应自行审查源码、依赖和命令行为。

来源信息

继续浏览同类 Skills