Your First Component
This tutorial walks you through generating a Button component, importing it into your app, and customising it with CSS variables — all without writing component boilerplate.
What you’ll build
Section titled “What you’ll build”A fully accessible, WCAG-compliant Button component with:
- Keyboard navigation and focus management
aria-disabledpattern (nodisabledattribute anti-pattern)- CSS custom property tokens for every visual aspect
- Light/dark mode support via
[data-theme="dark"]
Generate the Button component
Section titled “Generate the Button component”-
Open a Claude Code session at your project root and run:
/kit-add button -
Claude shows a preview of what will be generated before writing anything:
Will generate:src/components/fpkit/Button.tsx (new)src/components/fpkit/Button.scss (new)src/components/fpkit/ui.tsx (foundation — copied once)Dependencies: none -
Confirm to proceed. The files are written bottom-up (foundations first, then the component).
Inspect the generated files
Section titled “Inspect the generated files”src/components/fpkit/Button.tsx — a self-contained component with inline types:
import React from 'react';import { UI } from './ui';import './Button.scss';
export interface ButtonProps { children: React.ReactNode; variant?: 'primary' | 'secondary' | 'ghost' | 'danger'; size?: 'sm' | 'md' | 'lg'; disabled?: boolean; onClick?: React.MouseEventHandler<HTMLButtonElement>; className?: string;}
export function Button({ children, variant = 'primary', size = 'md', disabled = false, onClick, className,}: ButtonProps) { return ( <UI as="button" data-variant={variant} data-size={size} aria-disabled={disabled} onClick={disabled ? undefined : onClick} className={['btn', className].filter(Boolean).join(' ')} > {children} </UI> );}src/components/fpkit/Button.scss — token-first styles:
.btn { --btn-bg: var(--color-primary, #4f46e5); --btn-color: var(--color-text-inverse, #ffffff); --btn-border: transparent; --btn-radius: var(--radius-md, 0.375rem); --btn-padding-x: 1rem; --btn-padding-y: 0.5rem; --btn-font-size: 1rem; --btn-font-weight: 600; --btn-transition: background 150ms ease, box-shadow 150ms ease;
background: var(--btn-bg); color: var(--btn-color); border: 1px solid var(--btn-border); border-radius: var(--btn-radius); padding: var(--btn-padding-y) var(--btn-padding-x); font-size: var(--btn-font-size); font-weight: var(--btn-font-weight); cursor: pointer; transition: var(--btn-transition);
&[aria-disabled="true"] { opacity: 0.5; cursor: not-allowed; pointer-events: none; }
&:hover:not([aria-disabled="true"]) { --btn-bg: var(--color-primary-hover, #4338ca); }
&:focus-visible { outline: 2px solid var(--color-focus-ring, #6366f1); outline-offset: 2px; }
/* Variants */ &[data-variant="secondary"] { --btn-bg: var(--color-surface, #f8f8ff); --btn-color: var(--color-primary, #4f46e5); --btn-border: var(--color-border, #e2e2ef); }
&[data-variant="ghost"] { --btn-bg: transparent; --btn-color: var(--color-primary, #4f46e5); --btn-border: transparent; }
&[data-variant="danger"] { --btn-bg: var(--color-danger, #dc2626); --btn-color: var(--color-text-inverse, #ffffff); }
/* Sizes */ &[data-size="sm"] { --btn-font-size: 0.875rem; --btn-padding-x: 0.75rem; --btn-padding-y: 0.375rem; } &[data-size="lg"] { --btn-font-size: 1.125rem; --btn-padding-x: 1.5rem; --btn-padding-y: 0.75rem; }}Import and use it
Section titled “Import and use it”import { Button } from './components/fpkit/Button';
export default function App() { return ( <div> <Button variant="primary" onClick={() => alert('clicked')}> Get started </Button>
<Button variant="secondary">Learn more</Button>
{/* Accessible disabled — uses aria-disabled, not the disabled attribute */} <Button variant="primary" disabled> Unavailable </Button> </div> );}Customise with CSS variables
Section titled “Customise with CSS variables”Override any token in your own CSS — no need to touch the generated files:
.btn { --btn-radius: 9999px; /* pill shape */ --btn-font-weight: 500; /* lighter weight */ --btn-padding-x: 1.5rem; /* wider padding */}Add more components
Section titled “Add more components”Generate multiple components at once, and /kit-add resolves the dependency tree automatically:
/kit-add card badge dialogdialog depends on button and overlay. If those don’t exist yet, they’re generated first. If they already exist, they’re skipped and imported as-is.
Next: create a theme
Section titled “Next: create a theme”The --color-primary, --color-surface, and other variables used by the button come from your theme. Without a theme, the hardcoded fallbacks are used.
→ Continue to Your First Theme to generate a complete OKLCH palette that powers all your components.