From acss-kit
Use when generating fpkit-style React components into a developer's project. Markdown-as-source templates with embedded TSX/SCSS and accessibility patterns. No @fpkit/acss package required — only React + sass.
npx claudepluginhub shawn-sandy/agentic-acss-plugins --plugin acss-kitThis skill is limited to using the following tools:
Generate fpkit-style React components directly into a developer's project. No `@fpkit/acss` npm package required. Only React + sass needed.
references/accessibility.mdreferences/architecture.mdreferences/components/alert.mdreferences/components/button.mdreferences/components/card.mdreferences/components/catalog.mdreferences/components/checkbox.mdreferences/components/dialog.mdreferences/components/field.mdreferences/components/form.mdreferences/components/foundation.mdreferences/components/icon-button.mdreferences/components/icon.mdreferences/components/img.mdreferences/components/input.mdreferences/components/link.mdreferences/components/list.mdreferences/components/nav.mdreferences/components/popover.mdreferences/components/table.mdGuides Next.js Cache Components and Partial Prerendering (PPR) with cacheComponents enabled. Implements 'use cache', cacheLife(), cacheTag(), revalidateTag(), static/dynamic optimization, and cache debugging.
Guides building MCP servers enabling LLMs to interact with external services via tools. Covers best practices, TypeScript/Node (MCP SDK), Python (FastMCP).
Generates original PNG/PDF visual art via design philosophy manifestos for posters, graphics, and static designs on user request.
Generate fpkit-style React components directly into a developer's project. No @fpkit/acss npm package required. Only React + sass needed.
This skill generates self-contained, production-quality React components from markdown specs that embed the actual TSX/SCSS code as fenced code blocks alongside accessibility documentation. The developer owns the generated code and can freely modify it. Components use local imports — never @fpkit/acss.
sass or sass-embedded in devDependenciesRun this check at the start of every /kit-add invocation.
Read tsconfig.json and package.json to confirm React + TypeScript is present.
Read package.json. Look for sass or sass-embedded in devDependencies.
If neither is found, output:
sass or sass-embedded not found in devDependencies.
Run: npm install -D sass
Then re-run: /kit-add <component>
Stop. Do not generate any files.
Run python3 ${CLAUDE_PLUGIN_ROOT}/scripts/detect_target.py <project_root> to read or initialize .acss-target.json.
If the script returns "source": "generated", use the reported componentsDir. Skip the prompt.
If the script returns "source": "none", ask:
Where should components be generated? (default: src/components/fpkit/)
After the developer answers (or accepts the default), write .acss-target.json at the project root:
{ "componentsDir": "src/components/fpkit" }
Commit this file — /kit-add reads it on subsequent runs as the source of truth for import paths.
Remember the answer for the current session as well, so subsequent /kit-add calls don't re-read the file unnecessarily.
Check if ui.tsx exists in the target directory.
If not found:
${CLAUDE_PLUGIN_ROOT}/assets/foundation/ui.tsx into <target>/ui.tsxCreated ui.tsx (foundation component — do not delete)Read the component's reference doc:
references/components/{name}.mdreferences/components/catalog.mdIf the component is not in either, inform the developer. Run /kit-list to show available components.
Every reference doc has a Generation Contract section:
## Generation Contract
export_name: ComponentName
file: component-name.tsx
scss: component-name.scss
imports: UI from '../ui'
dependencies: [dep1, dep2]
This tells Claude exactly what files to create and what dependencies to resolve.
Reference docs follow the canonical embedded-markdown shape with three required sections beyond the Generation Contract — read them all before writing any files:
## TSX Template — fenced tsx block with the full component implementation. Copy this verbatim into the generated .tsx file. Substitute {{IMPORT_SOURCE:...}} / {{NAME}} / {{FIELDS}} placeholders at write time when present.## SCSS Template — fenced scss block with the canonical styles. Copy verbatim into the generated .scss file.## Accessibility — WCAG 2.2 AA criteria the component addresses (keyboard, ARIA, focus, contrast, target size). Don't strip a11y patterns out of the TSX/SCSS during generation; they're load-bearing.If a reference doc is missing any of these three sections, fall back to the older "Key Pattern" / "Full Implementation Reference" / "SCSS Pattern" shape. The catalog.md "Verification Status" table records which components have been migrated to the canonical shape; treat any others as legacy and synthesize from the available pieces.
Walk dependencies recursively using each dependency's own Generation Contract. Build the full list of files that will be created.
Example for Dialog:
dialog.tsx + dialog.scss
→ button.tsx + button.scss
→ icon-button.tsx + icon-button.scss
→ icon.tsx (no scss)
Before generating any files, display:
Generating the following files in src/components/fpkit/:
New:
ui.tsx (foundation — React only)
icon.tsx
button.tsx + button.scss
icon-button.tsx + icon-button.scss
dialog.tsx + dialog.scss
Skipped (already exist):
(none)
Proceed? [Enter to continue, Ctrl+C to cancel]
Wait for confirmation before proceeding.
Generate leaf dependencies first, then composite components.
Order example:
icon.tsx (no deps)button.tsx + button.scssicon-button.tsx + icon-button.scssdialog.tsx + dialog.scssFor each file:
.tsx)Imports:
// Always import UI from local path
import UI from '../ui'
import React from 'react'
// Other local deps
import Button from '../button/button'
Types:
// Inline all types in the component file
// Never import types from other generated components
export type ButtonProps = {
children?: React.ReactNode
disabled?: boolean
// ...
} & React.ComponentPropsWithoutRef<'button'>
No external imports other than React and local project files.
Condensed utilities:
useDisabledState — Inline the condensed ~50-line version from references/accessibility.mdresolveDisabledState — Inline as a one-liner: const resolveDisabledState = (d?: boolean, id?: boolean) => d ?? id ?? false.scss)Always use CSS custom properties with hardcoded fallbacks:
.btn {
font-size: var(--btn-fs, 0.9375rem);
padding-block: var(--btn-padding-block, calc(var(--btn-fs, 0.9375rem) * 0.5));
padding-inline: var(--btn-padding-inline, calc(var(--btn-fs, 0.9375rem) * 1.5));
border-radius: var(--btn-radius, 0.375rem);
background: var(--btn-bg, transparent);
color: var(--btn-color, var(--color-text, currentColor));
// Global token references MUST have fallbacks:
background: var(--btn-primary-bg, var(--color-primary, #0066cc));
}
Rules:
--{component}-{element?}-{variant?}-{property}--color-primary) always get hardcoded fallbacksreferences/css-variables.md for full naming conventionsAlways use aria-disabled instead of the native disabled attribute for buttons and interactive elements.
Why: Native disabled removes the element from keyboard tab order — keyboard and screen-reader users can't reach the control to discover it's disabled or access any explanation. aria-disabled keeps it focusable so screen readers can announce the disabled state.
Condensed useDisabledState (inline in button.tsx and any interactive component):
// Condensed useDisabledState — WCAG 2.1.1 compliant disabled pattern
// Uses aria-disabled instead of native disabled to maintain keyboard access
function useDisabledState(
disabled: boolean | undefined,
handlers: { onClick?: React.MouseEventHandler<HTMLButtonElement>; onKeyDown?: React.KeyboardEventHandler<HTMLButtonElement> } = {}
) {
const isDisabled = Boolean(disabled)
const disabledProps = {
'aria-disabled': isDisabled,
className: isDisabled ? 'is-disabled' : '',
}
const wrappedHandlers = {
onClick: handlers.onClick
? (e: React.MouseEvent<HTMLButtonElement>) => {
if (isDisabled) { e.preventDefault(); e.stopPropagation(); return }
handlers.onClick!(e)
}
: undefined,
onKeyDown: handlers.onKeyDown
? (e: React.KeyboardEvent<HTMLButtonElement>) => {
if (isDisabled) { e.preventDefault(); e.stopPropagation(); return }
handlers.onKeyDown!(e)
}
: undefined,
}
return { disabledProps, handlers: wrappedHandlers }
}
SCSS disabled styling:
.btn {
&[aria-disabled="true"],
&.is-disabled {
opacity: var(--btn-disabled-opacity, 0.6);
cursor: var(--btn-disabled-cursor, not-allowed);
pointer-events: none;
}
}
Always include visible focus indicators:
.btn:focus-visible {
outline: var(--btn-focus-outline, 2px solid currentColor);
outline-offset: var(--btn-focus-outline-offset, 2px);
}
Prefer semantic elements over roles:
<button> not <div role="button"><nav> not <div role="navigation"><dialog> not <div role="dialog">// {Component} component
// CSS variables with fallback defaults — override in :root or scoped selectors
.{component} {
// Layout
display: var(--{component}-display, block);
// Spacing
padding-block: var(--{component}-padding-block, 1rem);
padding-inline: var(--{component}-padding-inline, 1rem);
// Typography
font-size: var(--{component}-fs, 1rem);
font-weight: var(--{component}-fw, 400);
// Visual
background: var(--{component}-bg, transparent);
color: var(--{component}-color, currentColor);
border: var(--{component}-border, none);
border-radius: var(--{component}-radius, 0);
}
fpkit uses data-* attributes for variants (not BEM modifiers):
// Size variants via data-btn attribute
.btn[data-btn~="sm"] { font-size: var(--btn-size-sm, 0.8125rem); }
.btn[data-btn~="lg"] { font-size: var(--btn-size-lg, 1.125rem); }
.btn[data-btn~="block"] { width: 100%; }
// Style variants via data-style attribute
.btn[data-style="outline"] {
background: var(--btn-outline-bg, transparent);
border: var(--btn-outline-border, 1px solid currentColor);
}
// Color variants via data-color attribute
.btn[data-color="primary"] {
background: var(--btn-primary-bg, var(--color-primary, #0066cc));
color: var(--btn-primary-color, var(--color-text-inverse, #fff));
}
The [data-btn~="value"] selector matches space-separated words — data-btn="sm block" matches both [data-btn~="sm"] and [data-btn~="block"].
After all files are generated, show:
Generated components in src/components/fpkit/:
Created:
button/button.tsx
button/button.scss
Skipped (already existed):
(none)
Import and usage:
import Button from './components/fpkit/button/button'
import './components/fpkit/button/button.scss'
<Button type="button" onClick={handleClick}>Click me</Button>
<Button type="button" disabled>Disabled (stays focusable)</Button>
<Button type="button" data-color="primary" data-btn="lg">Primary Large</Button>
Read these before generating components:
| Document | Purpose |
|---|---|
references/architecture.md | UI base component, polymorphic pattern, as prop |
references/css-variables.md | CSS variable naming conventions, fallback strategy |
references/accessibility.md | WCAG patterns, aria-disabled, condensed useDisabledState |
references/composition.md | Compound components, generation decision tree |
references/components/catalog.md | Verification status table + remaining inline components (Badge, Tag, Heading, Text, Details, Progress) |
references/components/button.md | Button — canonical shape ✓ |
references/components/icon-button.md | IconButton (wraps Button + XOR aria-label/aria-labelledby) — canonical shape ✓ |
references/components/alert.md | Alert with severity levels, auto-dismiss — canonical shape ✓ |
references/components/card.md | Card compound component (Title, Content, Footer) — canonical shape ✓ |
references/components/dialog.md | Dialog with native <dialog> — canonical shape ✓ |
references/components/popover.md | Popover via native HTML Popover API — canonical shape ✓ |
references/components/table.md | Table compound (Caption, Head, Body, Row, HeaderCell, Cell) — canonical shape ✓ |
references/components/img.md | Img with lazy loading + SVG-gradient placeholder — canonical shape ✓ |
references/components/icon.md | Icon with built-in 9-icon SVG dispatch — canonical shape ✓ |
references/components/link.md | Link with auto security defaults — canonical shape ✓ |
references/components/list.md | List + List.ListItem (ul/ol/dl) — canonical shape ✓ |
references/components/field.md | Field (label + control wrapper) — canonical shape ✓ |
references/components/input.md | Input with validation states — canonical shape ✓ |
references/components/checkbox.md | Checkbox (wraps Input) — canonical shape ✓ |
references/components/form.md | Form composition (legacy bundled reference; superseded by component-form skill) |
references/components/nav.md | Nav compound component (List, Item) — legacy shape |
@fpkit/acss imports — all imports are localvar(--token) has a hardcoded fallbackdisabled for interactive componentsWhen adding or updating a component reference doc, follow the canonical embedded-markdown shape.
Every component reference doc must contain (in order):
**Verified against fpkit source:**. Records the upstream ref (e.g. @fpkit/acss@6.5.0) and any intentional divergences from upstream (inlined hooks, simplified compound APIs, dropped subcomponents). Future maintainers read this to understand why the vendored version diverges.## Overview — one-paragraph summary of the component's purpose.## Generation Contract — export_name, file, scss, imports, dependencies. The /kit-add workflow reads these fields verbatim.## Props Interface — TypeScript interface or type alias the component accepts.## TSX Template — fenced tsx block containing the full component code. Self-contained: imports only UI from '../ui', React, and other vendored components via relative paths. Never @fpkit/acss.## CSS Variables — fenced scss block listing the component's CSS custom properties with default values.## SCSS Template — fenced scss block containing the actual SCSS rules.## Accessibility — required. Document keyboard interaction, ARIA, focus management, target size, color contrast, and the WCAG 2.2 AA criteria addressed. The Accessibility section is load-bearing — don't strip a11y patterns out of the TSX/SCSS during generation.## Usage Examples — fenced tsx block showing common usage patterns.Most components live as reference docs at references/components/<name>.md. Composable, complex, or high-iteration components can be promoted to their own skill at skills/component-<name>/SKILL.md with discovery-friendly trigger phrases in the frontmatter description.
The component-form skill is the only per-component skill in 0.3.0. It serves as a pilot — adopt the per-component skill pattern for additional components only after observing the Form skill's trigger reliability in real-world usage.
Every new or migrated component gets an entry in references/components/catalog.md under "Verification Status":
| Component | Reference | Verified against | Status |
|-----------|-----------|------------------|--------|
| Foo | [`foo.md`](foo.md) | `@fpkit/acss@<version>` | New / Verified — <intentional divergences if any> |
This table is the single source of truth for which components have been migrated to the canonical shape.
Before authoring or backfilling a reference doc:
@fpkit/acss ceiling version to the matching git tag/SHA in the shawn-sandy/acss repo. If no matching tag exists for that npm version, use the closest tag and document the gap in the verification banner.https://github.com/shawn-sandy/acss/blob/<tag-or-sha>/packages/fpkit/src/<component>/... (full GitHub URL per repo policy — never blob/main).@fpkit/acss.