Skip to content

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.

A fully accessible, WCAG-compliant Button component with:

  • Keyboard navigation and focus management
  • aria-disabled pattern (no disabled attribute anti-pattern)
  • CSS custom property tokens for every visual aspect
  • Light/dark mode support via [data-theme="dark"]
  1. Open a Claude Code session at your project root and run:

    /kit-add button
  2. 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
  3. Confirm to proceed. The files are written bottom-up (foundations first, then the component).

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; }
}
src/App.tsx
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>
);
}

Override any token in your own CSS — no need to touch the generated files:

src/styles/overrides.css
.btn {
--btn-radius: 9999px; /* pill shape */
--btn-font-weight: 500; /* lighter weight */
--btn-padding-x: 1.5rem; /* wider padding */
}

Generate multiple components at once, and /kit-add resolves the dependency tree automatically:

/kit-add card badge dialog

dialog 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.

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.