Practices
Conventions that keep Aspyn products consistent — for humans and AI agents alike.
Always style through tokens
Never hardcode colors, radii, or shadows. Every visual property flows from a --as-* token via a Tailwind utility.
Do
<div className="bg-primary
text-primary-foreground
rounded-lg shadow-sm" />Don't
<div className="bg-[#4a6cf7]
text-white
rounded-[8px]" />Compose, don't configure
Components are small and composable, shadcn-style. Build features by composing primitives rather than adding boolean props to components.
Do
<Card>
<CardHeader>
<CardTitle>Revenue</CardTitle>
</CardHeader>
<CardContent>…</CardContent>
</Card>Don't
<Card
title="Revenue"
withHeader
headerSize="lg"
content={…}
/>Extend with className, escape with cn()
Every component accepts className, merged via cn() with conflict resolution — later classes win. Use asChild to render as a different element (e.g. a Next.js Link).
<Button asChild className="w-full">
<Link href="/billing">Manage billing</Link>
</Button>Accessibility is not optional
- Interactive components are built on Radix — keyboard and screen-reader support come standard. Don't bypass them with raw divs.
- Always pair
InputwithLabel(htmlFor/id). - Icon-only buttons need
aria-label. - Never remove focus rings; customize them through the
--as-ringtoken instead.
API & naming conventions
- Aspyn API responses and
expands[]values use snake_case. - URLs use hyphens-to-separate words.
- Component files are kebab-case (
dropdown-menu.tsx); exports are PascalCase.