Styled alert
Five alert treatments: soft, a left bar, glass, a glow and an icon pulse. Each is sized, driven by a tone prop (info, success, warning, danger) and fully token-based.
Installation
The styled layer is free and installs like any other ai2 component.
Run the following command
npx shadcn@latest add @ai2/alert-styledDependencies, the @ai2/tokens theme and the component file are installed together.
Install dependencies
npm install motionCopy the source
components/ui/alert-styled.tsx"use client"
import type * as React from "react"
import { motion, useReducedMotion } from "motion/react"
import { cn } from "@/lib/utils"
/* Styled alert family: 5 decorative alert surfaces. Colour comes ONLY from semantic tokens (alpha via color-mix); the tone prop maps to the info/success/warning/danger tokens through a small record. framer-motion is used only in IconPulse; the pulse stops under reduced-motion. Layout: a leading icon plus (title row plus description {children}). */
export type StyledSize = "sm" | "md" | "lg" | "xl"
export type StyledTone = "info" | "success" | "warning" | "danger"
const pad: Record<StyledSize, string> = {
sm: "p-3 text-sm",
md: "p-4 text-sm",
lg: "p-4 text-base",
xl: "p-5 text-base",
}
const base =
"relative flex w-full items-start gap-3 rounded-lg [&_svg]:size-4 [&_svg]:shrink-0 [&_i]:text-base [&_i]:leading-none"
/* Bastaki ikon tone rengi - tum varyasyonlarda paylasilir. */
const iconTone: Record<StyledTone, string> = {
info: "text-info",
success: "text-success",
warning: "text-warning-soft-foreground",
danger: "text-danger",
}
type BaseProps = React.ComponentProps<"div"> & {
size?: StyledSize
tone?: StyledTone
title?: React.ReactNode
icon?: React.ReactNode
}
/* Ortak govde: ikon sutunu + baslik/aciklama. */
function Body({
icon,
iconClassName,
title,
children,
}: {
icon?: React.ReactNode
iconClassName?: string
title?: React.ReactNode
children?: React.ReactNode
}) {
return (
<>
{icon ? (
<span
data-slot="styled-alert-icon"
className={cn("mt-0.5 flex shrink-0 items-center", iconClassName)}
>
{icon}
</span>
) : null}
<div data-slot="styled-alert-body" className="min-w-0 flex-1">
{title ? (
<div data-slot="styled-alert-title" className="font-medium tracking-tight">
{title}
</div>
) : null}
{children ? (
<div
data-slot="styled-alert-description"
className="text-sm opacity-90 [&_p]:leading-relaxed"
>
{children}
</div>
) : null}
</div>
</>
)
}
/* Soft: soft-token dolgu + eslesen kenar + tone ikon. Temiz varsayilan. */
const softTone: Record<StyledTone, string> = {
info: "bg-info-soft text-info-soft-foreground border border-info/30",
success: "bg-success-soft text-success-soft-foreground border border-success/30",
warning: "bg-warning-soft text-warning-soft-foreground border border-warning/40",
danger: "bg-danger-soft text-danger-soft-foreground border border-danger/30",
}
export function SoftAlert({
className,
size = "md",
tone = "info",
title,
children,
icon,
...props
}: BaseProps) {
return (
<div
data-slot="styled-alert"
data-tone={tone}
role="alert"
className={cn(base, pad[size], softTone[tone], className)}
{...props}
>
<Body icon={icon} iconClassName={iconTone[tone]} title={title}>
{children}
</Body>
</div>
)
}
/* LeftBar: notr yuzey + kalin tone-renkli sol kenar aksani. */
const leftBarTone: Record<StyledTone, string> = {
info: "border-l-info",
success: "border-l-success",
warning: "border-l-warning",
danger: "border-l-danger",
}
export function LeftBarAlert({
className,
size = "md",
tone = "info",
title,
children,
icon,
...props
}: BaseProps) {
return (
<div
data-slot="styled-alert"
data-tone={tone}
role="alert"
className={cn(
base,
pad[size],
"border border-border border-l-4 bg-surface-2 text-foreground",
leftBarTone[tone],
className
)}
{...props}
>
<Body icon={icon} iconClassName={iconTone[tone]} title={title}>
{children}
</Body>
</div>
)
}
/* Glass: frosted surface yuzey + tone-tinted kenar + backdrop-blur. */
const glassTone: Record<StyledTone, string> = {
info: "border-info/30",
success: "border-success/30",
warning: "border-warning/40",
danger: "border-danger/30",
}
export function GlassAlert({
className,
size = "md",
tone = "info",
title,
children,
icon,
...props
}: BaseProps) {
return (
<div
data-slot="styled-alert"
data-tone={tone}
role="alert"
className={cn(
base,
pad[size],
"border bg-surface-2/70 text-foreground supports-[backdrop-filter]:bg-surface-2/40 supports-[backdrop-filter]:backdrop-blur-md",
glassTone[tone],
className
)}
{...props}
>
<Body icon={icon} iconClassName={iconTone[tone]} title={title}>
{children}
</Body>
</div>
)
}
/* Glow: alert etrafinda tone-token isima (box-shadow). */
const glowTone: Record<StyledTone, string> = {
info: "shadow-[0_0_20px_-4px_var(--info)]",
success: "shadow-[0_0_20px_-4px_var(--success)]",
warning: "shadow-[0_0_20px_-4px_var(--warning)]",
danger: "shadow-[0_0_20px_-4px_var(--danger)]",
}
export function GlowAlert({
className,
size = "md",
tone = "info",
title,
children,
icon,
...props
}: BaseProps) {
return (
<div
data-slot="styled-alert"
data-tone={tone}
role="alert"
className={cn(
base,
pad[size],
"border border-border bg-surface-2 text-foreground",
glowTone[tone],
className
)}
{...props}
>
<Body icon={icon} iconClassName={iconTone[tone]} title={title}>
{children}
</Body>
</div>
)
}
/* IconPulse: a soft alert whose leading tone icon pulses gently for attention. The pulse stops under reduced-motion. */
export function IconPulseAlert({
className,
size = "md",
tone = "info",
title,
children,
icon,
...props
}: BaseProps) {
const reduce = useReducedMotion()
return (
<div
data-slot="styled-alert"
data-tone={tone}
role="alert"
className={cn(base, pad[size], softTone[tone], className)}
{...props}
>
{icon ? (
<motion.span
data-slot="styled-alert-icon"
className={cn("mt-0.5 flex shrink-0 items-center", iconTone[tone])}
animate={reduce ? undefined : { scale: [1, 1.18, 1], opacity: [1, 0.7, 1] }}
transition={{ duration: 2, ease: "easeInOut", repeat: Infinity }}
>
{icon}
</motion.span>
) : null}
<div data-slot="styled-alert-body" className="min-w-0 flex-1">
{title ? (
<div data-slot="styled-alert-title" className="font-medium tracking-tight">
{title}
</div>
) : null}
{children ? (
<div
data-slot="styled-alert-description"
className="text-sm opacity-90 [&_p]:leading-relaxed"
>
{children}
</div>
) : null}
</div>
</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.
Soft
Soft-token background with a matching border and tone icon.
import { SoftAlert } from "@/components/ui/alert-styled"
<SoftAlert tone="info" title="Heads up">A soft tinted alert for general information.</SoftAlert>Left bar
A neutral surface with a thick tone-colored left border.
import { LeftBarAlert } from "@/components/ui/alert-styled"
<LeftBarAlert tone="success" title="Saved">Your changes were saved.</LeftBarAlert>Glass
A frosted surface with a tone-tinted border and blur.
import { GlassAlert } from "@/components/ui/alert-styled"
<GlassAlert tone="warning" title="Careful">A frosted alert with a tone-tinted border.</GlassAlert>Glow
A tone-token glow around the alert.
import { GlowAlert } from "@/components/ui/alert-styled"
<GlowAlert tone="danger" title="Error">Something went wrong.</GlowAlert>Icon pulse
A soft alert whose tone icon gently pulses.
import { IconPulseAlert } from "@/components/ui/alert-styled"
<IconPulseAlert tone="info" title="New">The leading icon pulses to draw attention.</IconPulseAlert>ai2 Styled alert: 5 styled variations on the token system
The ai2 Styled alert are a set of 5 decorative button variations from the styled layer of the @ai2 design system, built around tone-driven alert banners. 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: framer-motion pulses the icon on the icon-pulse variant; the rest are static. The styled layer is opt-in, so the dependency only lands if you use it; the base components stay lean. Under reduced motion, the pulse stops and the alerts stay still.
What is in the ai2 Styled alert?
5 exports in one file: Soft, Left bar, Glass, Glow and Icon pulse. 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: framer-motion pulses the icon on the icon-pulse variant; the rest are static.
- Reduced-motion aware: Under prefers-reduced-motion, the pulse stops and the alerts stay still.
- 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 alert 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.