Loading...
Loading...
> Extend Fabrk's design system safely. Build custom components that maintain the terminal aesthetic and design consistency.
Learn the patterns and rules for creating custom components that integrate seamlessly with Fabrk's design system while maintaining accessibility and terminal aesthetics.
Comprehensive Documentation Available
docs/08-design/COMPONENT-AUTHORING.md. This page provides quick reference and examples.import { mode } from "@/design-system";
import { cn } from "@/lib/utils";
export function MyComponent({ className, ...props }) {
return (
<div
className={cn(
// Layout
"flex items-center gap-2",
// Terminal aesthetics
mode.radius, // rounded-none
mode.font, // font-mono
// Design tokens (NO hardcoded colors!)
"bg-card text-card-foreground",
"border border-border",
// States
"hover:bg-accent hover:text-accent-foreground",
"focus-visible:ring-2 focus-visible:ring-ring",
// Custom overrides
className
)}
{...props}
/>
);
}DO: Use Semantic Tokens
className="bg-background text-foreground" className="bg-card border-border" className="text-primary hover:bg-accent"
DO NOT: Hardcode Colors
className="bg-white text-black"
className="bg-gray-100 border-gray-300"
style={{ backgroundColor: "#ffffff" }}CORRECT: Sharp Corners
import { mode } from "@/design-system";
<Button className={mode.radius}>Click Me</Button>
// Renders: rounded-noneWRONG: Rounded Corners
<Button className="rounded-md">Click Me</Button> <Button className="rounded-lg">Click Me</Button>
CORRECT: Focus States
<button className="focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring" > Click Me </button>
WRONG: No Focus
<button className="focus:outline-none"> Click Me </button>
import { mode } from "@/design-system";
import { cn } from "@/lib/utils";
import { cva, type VariantProps } from "class-variance-authority";
const buttonVariants = cva(
cn(
"inline-flex items-center justify-center gap-2 text-xs",
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
"disabled:opacity-50 disabled:pointer-events-none",
mode.radius,
mode.font
),
{
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
outline: "border border-input bg-background hover:bg-accent",
},
size: {
default: "h-10 px-4 py-2",
sm: "h-8 px-3 py-1",
},
},
}
);import { mode } from "@/design-system";
import { cn } from "@/lib/utils";
export function MyCard({ className, ...props }) {
return (
<div
className={cn(
"bg-card text-card-foreground",
"border border-border",
mode.radius,
className
)}
{...props}
/>
);
}export const MyInput = React.forwardRef<HTMLInputElement, InputProps>(
({ className, type, ...props }, ref) => {
return (
<input
type={type}
className={cn(
"flex h-10 w-full px-3 py-2 text-xs",
"bg-background text-foreground",
"border border-input",
"placeholder:text-muted-foreground",
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
"disabled:cursor-not-allowed disabled:opacity-50",
mode.radius,
mode.font,
className
)}
ref={ref}
{...props}
/>
);
}
);All interactive elements must have visible focus indicators:
// CORRECT <button className="focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring" > Click Me </button> // WRONG - No visible focus! <button className="focus:outline-none">Click Me</button>
Icon-only buttons must have aria-label:
// CORRECT <Button size="icon" aria-label="Close dialog"> <X className="h-4 w-4" /> </Button> // WRONG - Screen reader can't announce purpose <Button size="icon"> <X className="h-4 w-4" /> </Button>
Required: 4.5:1 for normal text, 3:1 for large text and UI components.
Verify: Run npm run scan:hex to catch hardcoded colors.
All interactive elements must be keyboard accessible.
Best Practice: Use semantic HTML (<button>, <a>, <input>) instead of divs with click handlers.
npm run dev
Check:
Test all 12 themes:
Common issues:
# 1. Start dev server npm run dev # 2. Open Chrome DevTools → Lighthouse # 3. Run accessibility audit # 4. Target score: 90+
Common failures: