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 Input with Label (htmlFor/id).
  • Icon-only buttons need aria-label.
  • Never remove focus rings; customize them through the --as-ring token 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.