Styled item
Five list-row items: simple, icon, avatar, action and card. Each is sized, token-driven and lays out a leading media slot, a title, a description and a trailing slot.
Installation
The styled layer is free and installs like any other ai2 component.
Run the following command
npx shadcn@latest add @ai2/item-styledDependencies, the @ai2/tokens theme and the component file are installed together.
Install dependencies
npm install lucide-reactCopy the source
components/ui/item-styled.tsx"use client"
import type * as React from "react"
import { ChevronRight, Layers, Sparkles } from "lucide-react"
import { cn } from "@/lib/utils"
/* Item family: 5 decorative list rows (row / cell). An "item" = a leading media slot plus title plus an optional description plus an optional trailing slot (chevron or action). Used in lists, settings rows and menus. Colour comes ONLY from tokens; alpha via color-mix. No framer (no animation). Renders without props too. */
export type StyledSize = "sm" | "md" | "lg" | "xl"
const box: Record<StyledSize, string> = {
sm: "gap-2.5 rounded-lg p-2 text-sm",
md: "gap-3 rounded-lg p-3 text-sm",
lg: "gap-3.5 rounded-xl p-4 text-base",
xl: "gap-4 rounded-xl p-5 text-base",
}
const tile: Record<StyledSize, string> = {
sm: "size-8 rounded-md [&_svg]:size-4 [&_i]:text-base",
md: "size-9 rounded-md [&_svg]:size-4 [&_i]:text-base",
lg: "size-10 rounded-lg [&_svg]:size-5 [&_i]:text-lg",
xl: "size-12 rounded-lg [&_svg]:size-5 [&_i]:text-lg",
}
const avatarBox: Record<StyledSize, string> = {
sm: "size-8 text-xs",
md: "size-9 text-sm",
lg: "size-10 text-sm",
xl: "size-12 text-base",
}
const base =
"flex w-full items-center [&_svg]:shrink-0 [&_i]:not-italic [&_i]:leading-none"
const titleCls = "truncate font-medium text-foreground"
const descCls = "truncate text-muted-foreground text-[0.85em]"
type Props = React.ComponentProps<"div"> & {
size?: StyledSize
title?: React.ReactNode
description?: React.ReactNode
leading?: React.ReactNode
trailing?: React.ReactNode
}
/* Simple: title plus description, a trailing chevron, a token tint on hover. */
export function SimpleItem({
className,
size = "md",
title = "Account settings",
description = "Manage your profile and preferences",
leading,
trailing,
...props
}: Props) {
return (
<div
data-slot="styled-item"
className={cn(
base,
box[size],
"cursor-default transition-colors duration-(--motion-fast) ease-(--motion-ease) hover:bg-accent",
className
)}
{...props}
>
{leading}
<div className="flex min-w-0 flex-1 flex-col">
<span className={titleCls}>{title}</span>
{description ? <span className={descCls}>{description}</span> : null}
</div>
{trailing ?? <ChevronRight className="size-4 text-muted-foreground" />}
</div>
)
}
/* Icon: bastaki token ikon karesi (tile) + baslik/aciklama. */
export function IconItem({
className,
size = "md",
title = "Integrations",
description = "Connect your favorite tools",
leading = <Layers />,
trailing,
...props
}: Props) {
return (
<div data-slot="styled-item" className={cn(base, box[size], className)} {...props}>
<span
aria-hidden="true"
className={cn(
"inline-flex items-center justify-center text-brand",
tile[size],
"bg-[color-mix(in_oklab,var(--brand)_12%,transparent)]"
)}
>
{leading}
</span>
<div className="flex min-w-0 flex-1 flex-col">
<span className={titleCls}>{title}</span>
{description ? <span className={descCls}>{description}</span> : null}
</div>
{trailing}
</div>
)
}
/* Avatar: bastaki bas-harf avatari + baslik/aciklama + sondaki token badge. */
export function AvatarItem({
className,
size = "md",
title = "Ada Lovelace",
description = "ada@example.com",
leading = "AL",
trailing,
...props
}: Props) {
return (
<div data-slot="styled-item" className={cn(base, box[size], className)} {...props}>
<span
aria-hidden="true"
className={cn(
"inline-flex select-none items-center justify-center rounded-full bg-secondary font-medium text-secondary-foreground",
avatarBox[size]
)}
>
{leading}
</span>
<div className="flex min-w-0 flex-1 flex-col">
<span className={titleCls}>{title}</span>
{description ? <span className={descCls}>{description}</span> : null}
</div>
{trailing ?? (
<span className="inline-flex h-5 shrink-0 items-center rounded-md bg-[color-mix(in_oklab,var(--success)_14%,transparent)] px-2 text-xs font-medium text-success">
Active
</span>
)}
</div>
)
}
/* Action: baslik/aciklama + sondaki token aksiyon butonu (orn. "Manage"). */
export function ActionItem({
className,
size = "md",
title = "Billing plan",
description = "You are on the Pro plan",
leading,
trailing,
...props
}: Props) {
return (
<div data-slot="styled-item" className={cn(base, box[size], className)} {...props}>
{leading}
<div className="flex min-w-0 flex-1 flex-col">
<span className={titleCls}>{title}</span>
{description ? <span className={descCls}>{description}</span> : null}
</div>
{trailing ?? (
<button
type="button"
className="inline-flex h-8 shrink-0 items-center justify-center rounded-md border border-border bg-secondary px-3 text-xs font-medium text-secondary-foreground transition-colors duration-(--motion-fast) ease-(--motion-ease) hover:bg-accent focus-visible:outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50"
>
Manage
</button>
)}
</div>
)
}
/* Card: satirin tamami koseleri yuvarlatilmis token karti. */
export function CardItem({
className,
size = "md",
title = "New release",
description = "Version 2.0 is now available",
leading = <Sparkles />,
trailing,
...props
}: Props) {
return (
<div
data-slot="styled-item"
className={cn(
base,
box[size],
"border border-border bg-card text-card-foreground shadow-sm",
className
)}
{...props}
>
{leading ? (
<span
aria-hidden="true"
className={cn(
"inline-flex items-center justify-center text-brand",
tile[size],
"bg-[color-mix(in_oklab,var(--brand)_12%,transparent)]"
)}
>
{leading}
</span>
) : null}
<div className="flex min-w-0 flex-1 flex-col">
<span className={titleCls}>{title}</span>
{description ? <span className={descCls}>{description}</span> : null}
</div>
{trailing ?? <ChevronRight className="size-4 text-muted-foreground" />}
</div>
)
}Manual installs skip the @ai2/tokens theme, so add the token CSS from the theming guide or the tone colors will be missing.
Variations
5 takes on the same idea. Each is its own export, and every one accepts a size prop (sm, md, lg, xl) aligned to the base Button scale.
Simple
Title and description with a trailing chevron.
import { SimpleItem } from "@/components/ui/item-styled"
<SimpleItem />Icon
A leading token icon tile.
import { IconItem } from "@/components/ui/item-styled"
<IconItem />Avatar
A leading avatar with a trailing token badge.
import { AvatarItem } from "@/components/ui/item-styled"
<AvatarItem />Action
A trailing token action button.
import { ActionItem } from "@/components/ui/item-styled"
<ActionItem />Card
The item is a bordered token card.
import { CardItem } from "@/components/ui/item-styled"
<CardItem />ai2 Styled item: 5 styled variations on the token system
The ai2 Styled item are a set of 5 decorative button variations from the styled layer of the @ai2 design system, built around list rows and settings items. They are free and MIT licensed, and every color comes from a semantic token, so they theme with the rest of ai2 in light and dark.
Motion runs on framer-motion: hover states run on token CSS transitions. The styled layer is opt-in, so the dependency only lands if you use it; the base components stay lean. Under reduced motion, the hover transitions are disabled and items stay static.
What is in the ai2 Styled item?
5 exports in one file: Simple, Icon, Avatar, Action and Card. Each renders a native button and takes a size prop (sm, md, lg, xl) aligned to the base Button. They are separate from the base Button on purpose: the base keeps its clean variant, tone and size axes, while the styled layer carries the effects.
You own the file. Copy the one category file and you have all 5 variations, with no runtime dependency on ai2 itself.
Why use it
- On-system by construction: Every color resolves to an ai2 semantic token, so the buttons follow your theme in light and dark with no extra work.
- Effect without the sprawl: The decorations live in a dedicated styled file, so the base Button keeps its clean, predictable API.
- Accessible and honest: Each renders a real button element, keeps a visible focus ring, and respects prefers-reduced-motion.
Features
- Token-driven color: No hardcoded hex or oklch; the look recolors with your theme tokens.
- framer-motion: hover states run on token CSS transitions.
- Reduced-motion aware: Under prefers-reduced-motion, the hover transitions are disabled and items stay static.
- Size aligned to the base: Every variation takes sm, md, lg and xl matching the base Button height scale, so styled and base buttons line up in a row.
Production tips
- Use it for emphasis, not everywhere: Styled buttons draw the eye. Reserve them for the one action you want people to take on a screen, and use the base Button for the rest.
- Keep labels as verbs: The decoration adds weight, so a clear action label keeps the button scannable.
- Pick one variation per surface: The variations share a family; using two different ones in the same view competes for attention.
Works with the rest of ai2
The Styled item sit alongside the base Button and the rest of the @ai2 registry. They share the same token file, so a styled action next to a base button or a badge stays visually consistent in both modes.