Solid alerts
Five alerts built on a saturated solid tone fill rather than a soft tint: flat, a deep tone shadow, a gradient, a split two-band layout and a contrast ring. Text and icons use the matching tone foreground token, so contrast holds in light and dark. 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-solidDependencies, the @ai2/tokens theme and the component file are installed together.
Install dependencies
npm install lucide-reactCopy the source
components/ui/alert-solid.tsx"use client"
import type * as React from "react"
import { CircleAlert, CircleCheck, Info, TriangleAlert } from "lucide-react"
import { cn } from "@/lib/utils"
/* Solid alert family: 5 decorative alert surfaces. The shared idea is a FILLED
tone background - not the soft token but the tone itself as the background;
text and icon use the matching -foreground token, so contrast passes in both
themes. The variants change how the fill is treated (flat, deep shadow,
gradient, two bands, contrast ring). Color comes ONLY from semantic tokens,
via alpha color-mix or the tailwind opacity modifier. Anatomy (Berkay's
decision): the icon marks ONLY the title line - the icon and the title share
line 1, and the description starts BELOW the icon and takes the full width.
No motion, this family is entirely static.
Note: the gradient/split depth is produced by mixing with
var(--color-foreground). That direction is SAFE in both themes: in the light
theme foreground is dark, so the fill darkens and the light -foreground text
becomes even more readable; in the dark theme foreground is light, so the fill
lightens and the dark -foreground text becomes even more readable. */
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",
}
/* In Split the description band reaches the very edge: a negative margin equal to pad plus its own inner padding. */
const splitDesc: Record<StyledSize, string> = {
sm: "-mx-3 -mb-3 mt-2 px-3 py-2",
md: "-mx-4 -mb-4 mt-2 px-4 py-3",
lg: "-mx-4 -mb-4 mt-2 px-4 py-3",
xl: "-mx-5 -mb-5 mt-3 px-5 py-4",
}
/* Grid: icon column plus content. The icon is a direct child, so the [&>svg] / [&>i] pair (lucide <svg> and remixicon <i> compatibility) lives here. On a solid ground the icon colour comes from currentColor, that is from tone -foreground. */
const base =
"relative grid w-full grid-cols-[calc(var(--spacing)*4)_1fr] items-start gap-x-2 gap-y-0.5 rounded-lg [&>svg]:size-4 [&>svg]:shrink-0 [&>svg]:translate-y-0.5 [&>i]:block [&>i]:size-4 [&>i]:shrink-0 [&>i]:translate-y-0.5 [&>i]:text-base [&>i]:leading-none"
/* Tone -> varsayilan lucide ikon. */
const toneIcon: Record<StyledTone, React.ReactNode> = {
info: <Info />,
success: <CircleCheck />,
warning: <TriangleAlert />,
danger: <CircleAlert />,
}
type AlertProps = Omit<React.ComponentProps<"div">, "title"> & {
size?: StyledSize
tone?: StyledTone
title?: React.ReactNode
icon?: React.ReactNode
}
/* Shared shell: only the surface class changes. */
function Frame({
size,
tone,
title,
icon,
children,
className,
surface,
descClassName,
...props
}: Omit<AlertProps, "size" | "tone"> & {
size: StyledSize
tone: StyledTone
surface: string
descClassName?: string
}) {
return (
<div
data-slot="styled-alert"
data-tone={tone}
role="alert"
className={cn(base, pad[size], surface, className)}
{...props}
>
{icon ?? toneIcon[tone]}
<div
data-slot="styled-alert-title"
className="col-start-2 min-h-4 font-semibold tracking-tight"
>
{title}
</div>
<div
data-slot="styled-alert-description"
className={cn("col-span-2 col-start-1 text-sm [&_p]:leading-relaxed", descClassName)}
>
{children}
</div>
</div>
)
}
/* Dolu zemin + eslesen -foreground. Tum varyantlarin ortak temeli. */
const solidTone: Record<StyledTone, string> = {
info: "bg-info text-info-foreground",
success: "bg-success text-success-foreground",
warning: "bg-warning text-warning-foreground",
danger: "bg-danger text-danger-foreground",
}
/* Flat: sade dolu zemin, efekt yok. Ailenin temel hali. */
export function FlatAlert({
className,
size = "md",
tone = "info",
title = "Heads up",
children = "A flat solid tone fill with matching foreground text.",
icon,
...props
}: AlertProps) {
return (
<Frame
size={size}
tone={tone}
title={title}
icon={icon}
surface={solidTone[tone]}
className={className}
{...props}
>
{children}
</Frame>
)
}
/* Deep: dolu zemin + tone renkli dis golge ve ustte ince ic isik cizgisi.
Yuzeyi sayfadan kaldirir. */
const deepTone: Record<StyledTone, string> = {
info: "shadow-[0_10px_24px_-8px_color-mix(in_oklab,var(--color-info)_65%,transparent),inset_0_1px_0_color-mix(in_oklab,var(--color-info-foreground)_28%,transparent)]",
success:
"shadow-[0_10px_24px_-8px_color-mix(in_oklab,var(--color-success)_65%,transparent),inset_0_1px_0_color-mix(in_oklab,var(--color-success-foreground)_28%,transparent)]",
warning:
"shadow-[0_10px_24px_-8px_color-mix(in_oklab,var(--color-warning)_65%,transparent),inset_0_1px_0_color-mix(in_oklab,var(--color-warning-foreground)_28%,transparent)]",
danger:
"shadow-[0_10px_24px_-8px_color-mix(in_oklab,var(--color-danger)_65%,transparent),inset_0_1px_0_color-mix(in_oklab,var(--color-danger-foreground)_28%,transparent)]",
}
export function DeepAlert({
className,
size = "md",
tone = "info",
title = "Heads up",
children = "A solid fill lifted by a tone-colored drop shadow.",
icon,
...props
}: AlertProps) {
return (
<Frame
size={size}
tone={tone}
title={title}
icon={icon}
surface={cn(solidTone[tone], deepTone[tone])}
className={className}
{...props}
>
{children}
</Frame>
)
}
/* Gradient: a diagonal transition from tone towards foreground. The direction INCREASES contrast in both themes (see the note at the top of the file). */
const gradientTone: Record<StyledTone, string> = {
info: "bg-gradient-to-br from-info to-[color-mix(in_oklab,var(--color-info)_76%,var(--color-foreground))]",
success:
"bg-gradient-to-br from-success to-[color-mix(in_oklab,var(--color-success)_76%,var(--color-foreground))]",
warning:
"bg-gradient-to-br from-warning to-[color-mix(in_oklab,var(--color-warning)_76%,var(--color-foreground))]",
danger:
"bg-gradient-to-br from-danger to-[color-mix(in_oklab,var(--color-danger)_76%,var(--color-foreground))]",
}
export function GradientAlert({
className,
size = "md",
tone = "info",
title = "Heads up",
children = "A diagonal gradient across two steps of the same tone.",
icon,
...props
}: AlertProps) {
return (
<Frame
size={size}
tone={tone}
title={title}
icon={icon}
surface={cn(solidTone[tone], gradientTone[tone])}
className={className}
{...props}
>
{children}
</Frame>
)
}
/* Split: two bands of the same fill - the title row is the light band, the description a full-width darker band. The description reaches the edge, hence overflow-hidden on the root. */
export function SplitAlert({
className,
size = "md",
tone = "info",
title = "Heads up",
children = "The description sits on a second, deeper band of the same tone.",
icon,
...props
}: AlertProps) {
return (
<Frame
size={size}
tone={tone}
title={title}
icon={icon}
surface={cn("overflow-hidden", solidTone[tone])}
descClassName={cn(
"bg-[color-mix(in_oklab,var(--color-foreground)_14%,transparent)]",
splitDesc[size]
)}
className={className}
{...props}
>
{children}
</Frame>
)
}
/* Contrast: a filled background + a tone ring leaving a gap from the background.
It separates the surface sharply from the page. */
const contrastTone: Record<StyledTone, string> = {
info: "ring-2 ring-info ring-offset-2 ring-offset-background",
success: "ring-2 ring-success ring-offset-2 ring-offset-background",
warning: "ring-2 ring-warning ring-offset-2 ring-offset-background",
danger: "ring-2 ring-danger ring-offset-2 ring-offset-background",
}
export function ContrastAlert({
className,
size = "md",
tone = "info",
title = "Heads up",
children = "A solid fill ringed with an offset gap against the page.",
icon,
...props
}: AlertProps) {
return (
<Frame
size={size}
tone={tone}
title={title}
icon={icon}
surface={cn(solidTone[tone], contrastTone[tone])}
className={className}
{...props}
>
{children}
</Frame>
)
}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.
Flat
A plain solid tone fill, the base of the family.
import { FlatAlert } from "@/components/ui/alert-solid"
<FlatAlert tone="info" title="Heads up">A flat solid fill with matching foreground text.</FlatAlert>Deep
A solid fill lifted by a tone shadow and an inset top highlight.
import { DeepAlert } from "@/components/ui/alert-solid"
<DeepAlert tone="success" title="Deployed">A tone-colored drop shadow lifts the surface.</DeepAlert>Gradient
A diagonal gradient between two steps of the same tone.
import { GradientAlert } from "@/components/ui/alert-solid"
<GradientAlert tone="danger" title="Failed">A diagonal gradient across two steps of the tone.</GradientAlert>Split
Two bands of one fill: a light title row and a deeper description row.
import { SplitAlert } from "@/components/ui/alert-solid"
<SplitAlert tone="warning" title="Quota low">The description sits on a second, deeper band.</SplitAlert>Contrast
A solid fill ringed with an offset gap against the page.
import { ContrastAlert } from "@/components/ui/alert-solid"
<ContrastAlert tone="info" title="Update">An offset tone ring separates it from the page.</ContrastAlert>ai2 Solid alerts: 5 styled variations on the token system
The ai2 Solid alerts are a set of 5 decorative button variations from the styled layer of the @ai2 design system, built around alerts on a saturated solid tone fill with a matching foreground token. 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: there is no motion in this family; every variation is static. The styled layer is opt-in, so the dependency only lands if you use it; the base components stay lean. Under reduced motion, nothing changes, because the alerts never move.
What is in the ai2 Solid alerts?
5 exports in one file: Flat, Deep, Gradient, Split and Contrast. 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: there is no motion in this family; every variation is static.
- Reduced-motion aware: Under prefers-reduced-motion, nothing changes, because the alerts never move.
- 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 Solid alerts 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.