Notice
A line of context on a tinted well, in the flow of a form, a dialog or a page. Not a card, not a toast.
import { Notice } from '@/components/notice'
import { EnvelopeIcon } from '@heroicons/react/16/solid'
export default function Example() {
return (
<Notice icon={EnvelopeIcon}>
We will send a verification code to <strong className="font-medium text-zinc-950 dark:text-white">l•••@example.com</strong>.
</Notice>
)
}<script setup lang="ts">
import Notice from '@/components/notice.vue'
import { EnvelopeIcon } from '@heroicons/vue/16/solid'
</script>
<template>
<Notice :icon="EnvelopeIcon">
We will send a verification code to
<strong class="font-medium text-zinc-950 dark:text-white">l•••@example.com</strong>.
</Notice>
</template>Installation
Copy these files into the folder where you keep your components. They import each other with relative paths, so the folder can live anywhere. The packages they rely on are listed on the Installation page.
import clsx from 'clsx'
import type React from 'react'
type NoticeColors = {
background: string
icon: string
text: string
title?: string
body?: string
plainBackground?: string
plainIcon?: string
barBackground: string
bar: string
}
const colors = {
red: {
background: 'bg-red-500/10',
icon: 'text-red-600 dark:text-red-400',
text: 'text-red-700 dark:text-red-400',
title: 'text-red-700 dark:text-red-400',
body: 'text-red-700/80 dark:text-red-400/80',
barBackground: 'bg-red-50/60 ring-red-200/60 dark:bg-red-500/8 dark:ring-red-400/15',
bar: 'bg-red-500 dark:bg-red-400',
},
orange: {
background: 'bg-orange-500/10',
icon: 'text-orange-600 dark:text-orange-400',
text: 'text-orange-700 dark:text-orange-400',
barBackground: 'bg-orange-50/60 ring-orange-200/60 dark:bg-orange-500/8 dark:ring-orange-400/15',
bar: 'bg-orange-500 dark:bg-orange-400',
},
amber: {
background: 'bg-amber-500/10',
icon: 'text-amber-600 dark:text-amber-400',
text: 'text-amber-700 dark:text-amber-400',
plainBackground: 'bg-amber-400/15 dark:bg-amber-400/10',
plainIcon: 'text-amber-500 dark:text-amber-400',
barBackground: 'bg-amber-50/60 ring-amber-200/60 dark:bg-amber-500/8 dark:ring-amber-400/15',
bar: 'bg-amber-500 dark:bg-amber-400',
},
yellow: {
background: 'bg-yellow-500/10',
icon: 'text-yellow-600 dark:text-yellow-400',
text: 'text-yellow-700 dark:text-yellow-400',
barBackground: 'bg-yellow-50/60 ring-yellow-200/60 dark:bg-yellow-500/8 dark:ring-yellow-400/15',
bar: 'bg-yellow-500 dark:bg-yellow-400',
},
lime: {
background: 'bg-lime-500/10',
icon: 'text-lime-600 dark:text-lime-400',
text: 'text-lime-700 dark:text-lime-400',
barBackground: 'bg-lime-50/60 ring-lime-200/60 dark:bg-lime-500/8 dark:ring-lime-400/15',
bar: 'bg-lime-500 dark:bg-lime-400',
},
green: {
background: 'bg-green-500/5 ring-1 ring-green-600/20 ring-inset dark:bg-green-500/10 dark:ring-green-500/20',
icon: 'text-green-600 dark:text-green-400',
text: 'text-zinc-600 dark:text-zinc-400',
barBackground: 'bg-green-50/60 ring-green-200/60 dark:bg-green-500/8 dark:ring-green-400/15',
bar: 'bg-green-500 dark:bg-green-400',
},
emerald: {
background: 'bg-emerald-500/10',
icon: 'text-emerald-600 dark:text-emerald-400',
text: 'text-emerald-700 dark:text-emerald-400',
barBackground: 'bg-emerald-50/60 ring-emerald-200/60 dark:bg-emerald-500/8 dark:ring-emerald-400/15',
bar: 'bg-emerald-500 dark:bg-emerald-400',
},
teal: {
background: 'bg-teal-500/10',
icon: 'text-teal-600 dark:text-teal-400',
text: 'text-teal-700 dark:text-teal-400',
barBackground: 'bg-teal-50/60 ring-teal-200/60 dark:bg-teal-500/8 dark:ring-teal-400/15',
bar: 'bg-teal-500 dark:bg-teal-400',
},
cyan: {
background: 'bg-cyan-500/10',
icon: 'text-cyan-600 dark:text-cyan-400',
text: 'text-cyan-700 dark:text-cyan-400',
barBackground: 'bg-cyan-50/60 ring-cyan-200/60 dark:bg-cyan-500/8 dark:ring-cyan-400/15',
bar: 'bg-cyan-500 dark:bg-cyan-400',
},
sky: {
background: 'bg-sky-500/10',
icon: 'text-sky-600 dark:text-sky-400',
text: 'text-sky-700 dark:text-sky-400',
barBackground: 'bg-sky-50/60 ring-sky-200/60 dark:bg-sky-500/8 dark:ring-sky-400/15',
bar: 'bg-sky-500 dark:bg-sky-400',
},
blue: {
background: 'bg-blue-500/10',
icon: 'text-blue-600 dark:text-blue-400',
text: 'text-blue-700 dark:text-blue-400',
barBackground: 'bg-blue-50/60 ring-blue-200/60 dark:bg-blue-500/8 dark:ring-blue-400/15',
bar: 'bg-blue-500 dark:bg-blue-400',
},
indigo: {
background: 'bg-indigo-500/10',
icon: 'text-indigo-600 dark:text-indigo-400',
text: 'text-indigo-700 dark:text-indigo-400',
barBackground: 'bg-indigo-50/60 ring-indigo-200/60 dark:bg-indigo-500/8 dark:ring-indigo-400/15',
bar: 'bg-indigo-500 dark:bg-indigo-400',
},
violet: {
background: 'bg-violet-500/10',
icon: 'text-violet-600 dark:text-violet-400',
text: 'text-violet-700 dark:text-violet-400',
barBackground: 'bg-violet-50/60 ring-violet-200/60 dark:bg-violet-500/8 dark:ring-violet-400/15',
bar: 'bg-violet-500 dark:bg-violet-400',
},
purple: {
background: 'bg-purple-500/10',
icon: 'text-purple-600 dark:text-purple-400',
text: 'text-purple-700 dark:text-purple-400',
barBackground: 'bg-purple-50/60 ring-purple-200/60 dark:bg-purple-500/8 dark:ring-purple-400/15',
bar: 'bg-purple-500 dark:bg-purple-400',
},
fuchsia: {
background: 'bg-fuchsia-500/10',
icon: 'text-fuchsia-600 dark:text-fuchsia-400',
text: 'text-fuchsia-700 dark:text-fuchsia-400',
barBackground: 'bg-fuchsia-50/60 ring-fuchsia-200/60 dark:bg-fuchsia-500/8 dark:ring-fuchsia-400/15',
bar: 'bg-fuchsia-500 dark:bg-fuchsia-400',
},
pink: {
background: 'bg-pink-500/10',
icon: 'text-pink-600 dark:text-pink-400',
text: 'text-pink-700 dark:text-pink-400',
barBackground: 'bg-pink-50/60 ring-pink-200/60 dark:bg-pink-500/8 dark:ring-pink-400/15',
bar: 'bg-pink-500 dark:bg-pink-400',
},
rose: {
background: 'bg-rose-500/10',
icon: 'text-rose-600 dark:text-rose-400',
text: 'text-rose-700 dark:text-rose-400',
barBackground: 'bg-rose-50/60 ring-rose-200/60 dark:bg-rose-500/8 dark:ring-rose-400/15',
bar: 'bg-rose-500 dark:bg-rose-400',
},
slate: {
background: 'bg-slate-950/2.5 dark:bg-white/2.5',
icon: 'text-slate-500 dark:text-slate-400',
text: 'text-slate-600 dark:text-slate-400',
barBackground: 'bg-slate-50/60 ring-slate-200/60 dark:bg-white/2 dark:ring-white/10',
bar: 'bg-slate-400 dark:bg-slate-500',
},
gray: {
background: 'bg-gray-950/2.5 dark:bg-white/2.5',
icon: 'text-gray-500 dark:text-gray-400',
text: 'text-gray-600 dark:text-gray-400',
barBackground: 'bg-gray-50/60 ring-gray-200/60 dark:bg-white/2 dark:ring-white/10',
bar: 'bg-gray-400 dark:bg-gray-500',
},
zinc: {
background: 'bg-zinc-950/2.5 dark:bg-white/2.5',
icon: 'text-zinc-500 dark:text-zinc-400',
text: 'text-zinc-600 dark:text-zinc-400',
barBackground: 'bg-zinc-50/60 ring-zinc-200/60 dark:bg-white/2 dark:ring-white/10',
bar: 'bg-zinc-400 dark:bg-zinc-500',
},
neutral: {
background: 'bg-neutral-950/2.5 dark:bg-white/2.5',
icon: 'text-neutral-500 dark:text-neutral-400',
text: 'text-neutral-600 dark:text-neutral-400',
barBackground: 'bg-neutral-50/60 ring-neutral-200/60 dark:bg-white/2 dark:ring-white/10',
bar: 'bg-neutral-400 dark:bg-neutral-500',
},
stone: {
background: 'bg-stone-950/2.5 dark:bg-white/2.5',
icon: 'text-stone-500 dark:text-stone-400',
text: 'text-stone-600 dark:text-stone-400',
barBackground: 'bg-stone-50/60 ring-stone-200/60 dark:bg-white/2 dark:ring-white/10',
bar: 'bg-stone-400 dark:bg-stone-500',
},
mauve: {
background: 'bg-mauve-950/2.5 dark:bg-white/2.5',
icon: 'text-mauve-500 dark:text-mauve-400',
text: 'text-mauve-600 dark:text-mauve-400',
barBackground: 'bg-mauve-50/60 ring-mauve-200/60 dark:bg-white/2 dark:ring-white/10',
bar: 'bg-mauve-400 dark:bg-mauve-500',
},
olive: {
background: 'bg-olive-950/2.5 dark:bg-white/2.5',
icon: 'text-olive-500 dark:text-olive-400',
text: 'text-olive-600 dark:text-olive-400',
barBackground: 'bg-olive-50/60 ring-olive-200/60 dark:bg-white/2 dark:ring-white/10',
bar: 'bg-olive-400 dark:bg-olive-500',
},
mist: {
background: 'bg-mist-950/2.5 dark:bg-white/2.5',
icon: 'text-mist-500 dark:text-mist-400',
text: 'text-mist-600 dark:text-mist-400',
barBackground: 'bg-mist-50/60 ring-mist-200/60 dark:bg-white/2 dark:ring-white/10',
bar: 'bg-mist-400 dark:bg-mist-500',
},
taupe: {
background: 'bg-taupe-950/2.5 dark:bg-white/2.5',
icon: 'text-taupe-500 dark:text-taupe-400',
text: 'text-taupe-600 dark:text-taupe-400',
barBackground: 'bg-taupe-50/60 ring-taupe-200/60 dark:bg-white/2 dark:ring-white/10',
bar: 'bg-taupe-400 dark:bg-taupe-500',
},
} satisfies Record<string, NoticeColors>
const sizes = {
md: 'rounded-lg px-3.5 py-3',
lg: 'rounded-xl px-4 py-3.5',
}
const text = 'text-base/6 sm:text-sm/6'
type NoticeProps = {
tone?: keyof typeof colors
variant?: 'filled' | 'bar'
size?: keyof typeof sizes
icon?: React.ComponentType<{ className?: string }>
title?: React.ReactNode
action?: React.ReactNode
} & React.ComponentPropsWithoutRef<'div'>
export function Notice({
tone = 'zinc',
variant = 'filled',
size = 'md',
icon: Icon,
title,
action,
className,
children,
...props
}: NoticeProps) {
let color: NoticeColors = colors[tone]
let titleColor = color.title ?? 'text-zinc-950 dark:text-white'
let bodyColor = color.body ?? 'text-zinc-600 dark:text-zinc-400'
if (variant === 'bar') {
return (
<div {...props} className={clsx(className, 'relative rounded-xl py-4 pr-5 pl-6 ring-1', color.barBackground)}>
<span aria-hidden="true" className={clsx('absolute inset-y-4 left-3 w-0.75 rounded-full', color.bar)} />
<div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
<div className="text-sm/relaxed text-zinc-600 dark:text-zinc-300">
{title && <p className="font-semibold text-zinc-950 dark:text-white">{title}</p>}
{children && <div className={clsx(title && 'mt-1')}>{children}</div>}
</div>
{action && <div className="flex shrink-0 items-center gap-2 sm:justify-end">{action}</div>}
</div>
</div>
)
}
if (action) {
return (
<div
{...props}
className={clsx(className, 'flex flex-wrap items-center gap-x-4 gap-y-3', sizes[size], color.background)}
>
{Icon && <Icon className={clsx('size-4 h-lh shrink-0', color.icon)} />}
<div className={clsx('min-w-0 flex-1 text-pretty', text, color.text)}>
{title && <p className={clsx('font-medium', titleColor)}>{title}</p>}
{children}
</div>
<div className="flex shrink-0 items-center gap-3 max-sm:w-full">{action}</div>
</div>
)
}
if (title) {
return (
<div
{...props}
className={clsx(
className,
Icon ? 'grid grid-cols-[--spacing(4)_1fr] gap-x-2.5 gap-y-1' : 'flex flex-col gap-y-1',
sizes[size],
color.background
)}
>
{Icon && <Icon className={clsx('row-start-1 size-4 shrink-0 self-center', color.icon)} />}
<p className={clsx('font-medium', text, titleColor)}>{title}</p>
{children && <div className={clsx(Icon && 'col-start-2', 'text-pretty', text, bodyColor)}>{children}</div>}
</div>
)
}
return (
<div {...props} className={clsx(className, 'flex items-start gap-2.5', sizes[size], color.plainBackground ?? color.background)}>
{Icon && <Icon className={clsx('size-4 h-lh shrink-0', color.plainIcon ?? color.icon)} />}
<div className={clsx('min-w-0 text-pretty', text, color.text)}>{children}</div>
</div>
)
}<script setup lang="ts">
import type { Component } from 'vue'
import { computed, useSlots } from 'vue'
type NoticeColors = {
background: string
icon: string
text: string
title?: string
body?: string
plainBackground?: string
plainIcon?: string
barBackground: string
bar: string
}
const colors = {
red: {
background: 'bg-red-500/10',
icon: 'text-red-600 dark:text-red-400',
text: 'text-red-700 dark:text-red-400',
title: 'text-red-700 dark:text-red-400',
body: 'text-red-700/80 dark:text-red-400/80',
barBackground: 'bg-red-50/60 ring-red-200/60 dark:bg-red-500/8 dark:ring-red-400/15',
bar: 'bg-red-500 dark:bg-red-400',
},
orange: {
background: 'bg-orange-500/10',
icon: 'text-orange-600 dark:text-orange-400',
text: 'text-orange-700 dark:text-orange-400',
barBackground: 'bg-orange-50/60 ring-orange-200/60 dark:bg-orange-500/8 dark:ring-orange-400/15',
bar: 'bg-orange-500 dark:bg-orange-400',
},
amber: {
background: 'bg-amber-500/10',
icon: 'text-amber-600 dark:text-amber-400',
text: 'text-amber-700 dark:text-amber-400',
plainBackground: 'bg-amber-400/15 dark:bg-amber-400/10',
plainIcon: 'text-amber-500 dark:text-amber-400',
barBackground: 'bg-amber-50/60 ring-amber-200/60 dark:bg-amber-500/8 dark:ring-amber-400/15',
bar: 'bg-amber-500 dark:bg-amber-400',
},
yellow: {
background: 'bg-yellow-500/10',
icon: 'text-yellow-600 dark:text-yellow-400',
text: 'text-yellow-700 dark:text-yellow-400',
barBackground: 'bg-yellow-50/60 ring-yellow-200/60 dark:bg-yellow-500/8 dark:ring-yellow-400/15',
bar: 'bg-yellow-500 dark:bg-yellow-400',
},
lime: {
background: 'bg-lime-500/10',
icon: 'text-lime-600 dark:text-lime-400',
text: 'text-lime-700 dark:text-lime-400',
barBackground: 'bg-lime-50/60 ring-lime-200/60 dark:bg-lime-500/8 dark:ring-lime-400/15',
bar: 'bg-lime-500 dark:bg-lime-400',
},
green: {
background: 'bg-green-500/5 ring-1 ring-green-600/20 ring-inset dark:bg-green-500/10 dark:ring-green-500/20',
icon: 'text-green-600 dark:text-green-400',
text: 'text-zinc-600 dark:text-zinc-400',
barBackground: 'bg-green-50/60 ring-green-200/60 dark:bg-green-500/8 dark:ring-green-400/15',
bar: 'bg-green-500 dark:bg-green-400',
},
emerald: {
background: 'bg-emerald-500/10',
icon: 'text-emerald-600 dark:text-emerald-400',
text: 'text-emerald-700 dark:text-emerald-400',
barBackground: 'bg-emerald-50/60 ring-emerald-200/60 dark:bg-emerald-500/8 dark:ring-emerald-400/15',
bar: 'bg-emerald-500 dark:bg-emerald-400',
},
teal: {
background: 'bg-teal-500/10',
icon: 'text-teal-600 dark:text-teal-400',
text: 'text-teal-700 dark:text-teal-400',
barBackground: 'bg-teal-50/60 ring-teal-200/60 dark:bg-teal-500/8 dark:ring-teal-400/15',
bar: 'bg-teal-500 dark:bg-teal-400',
},
cyan: {
background: 'bg-cyan-500/10',
icon: 'text-cyan-600 dark:text-cyan-400',
text: 'text-cyan-700 dark:text-cyan-400',
barBackground: 'bg-cyan-50/60 ring-cyan-200/60 dark:bg-cyan-500/8 dark:ring-cyan-400/15',
bar: 'bg-cyan-500 dark:bg-cyan-400',
},
sky: {
background: 'bg-sky-500/10',
icon: 'text-sky-600 dark:text-sky-400',
text: 'text-sky-700 dark:text-sky-400',
barBackground: 'bg-sky-50/60 ring-sky-200/60 dark:bg-sky-500/8 dark:ring-sky-400/15',
bar: 'bg-sky-500 dark:bg-sky-400',
},
blue: {
background: 'bg-blue-500/10',
icon: 'text-blue-600 dark:text-blue-400',
text: 'text-blue-700 dark:text-blue-400',
barBackground: 'bg-blue-50/60 ring-blue-200/60 dark:bg-blue-500/8 dark:ring-blue-400/15',
bar: 'bg-blue-500 dark:bg-blue-400',
},
indigo: {
background: 'bg-indigo-500/10',
icon: 'text-indigo-600 dark:text-indigo-400',
text: 'text-indigo-700 dark:text-indigo-400',
barBackground: 'bg-indigo-50/60 ring-indigo-200/60 dark:bg-indigo-500/8 dark:ring-indigo-400/15',
bar: 'bg-indigo-500 dark:bg-indigo-400',
},
violet: {
background: 'bg-violet-500/10',
icon: 'text-violet-600 dark:text-violet-400',
text: 'text-violet-700 dark:text-violet-400',
barBackground: 'bg-violet-50/60 ring-violet-200/60 dark:bg-violet-500/8 dark:ring-violet-400/15',
bar: 'bg-violet-500 dark:bg-violet-400',
},
purple: {
background: 'bg-purple-500/10',
icon: 'text-purple-600 dark:text-purple-400',
text: 'text-purple-700 dark:text-purple-400',
barBackground: 'bg-purple-50/60 ring-purple-200/60 dark:bg-purple-500/8 dark:ring-purple-400/15',
bar: 'bg-purple-500 dark:bg-purple-400',
},
fuchsia: {
background: 'bg-fuchsia-500/10',
icon: 'text-fuchsia-600 dark:text-fuchsia-400',
text: 'text-fuchsia-700 dark:text-fuchsia-400',
barBackground: 'bg-fuchsia-50/60 ring-fuchsia-200/60 dark:bg-fuchsia-500/8 dark:ring-fuchsia-400/15',
bar: 'bg-fuchsia-500 dark:bg-fuchsia-400',
},
pink: {
background: 'bg-pink-500/10',
icon: 'text-pink-600 dark:text-pink-400',
text: 'text-pink-700 dark:text-pink-400',
barBackground: 'bg-pink-50/60 ring-pink-200/60 dark:bg-pink-500/8 dark:ring-pink-400/15',
bar: 'bg-pink-500 dark:bg-pink-400',
},
rose: {
background: 'bg-rose-500/10',
icon: 'text-rose-600 dark:text-rose-400',
text: 'text-rose-700 dark:text-rose-400',
barBackground: 'bg-rose-50/60 ring-rose-200/60 dark:bg-rose-500/8 dark:ring-rose-400/15',
bar: 'bg-rose-500 dark:bg-rose-400',
},
slate: {
background: 'bg-slate-950/2.5 dark:bg-white/2.5',
icon: 'text-slate-500 dark:text-slate-400',
text: 'text-slate-600 dark:text-slate-400',
barBackground: 'bg-slate-50/60 ring-slate-200/60 dark:bg-white/2 dark:ring-white/10',
bar: 'bg-slate-400 dark:bg-slate-500',
},
gray: {
background: 'bg-gray-950/2.5 dark:bg-white/2.5',
icon: 'text-gray-500 dark:text-gray-400',
text: 'text-gray-600 dark:text-gray-400',
barBackground: 'bg-gray-50/60 ring-gray-200/60 dark:bg-white/2 dark:ring-white/10',
bar: 'bg-gray-400 dark:bg-gray-500',
},
zinc: {
background: 'bg-zinc-950/2.5 dark:bg-white/2.5',
icon: 'text-zinc-500 dark:text-zinc-400',
text: 'text-zinc-600 dark:text-zinc-400',
barBackground: 'bg-zinc-50/60 ring-zinc-200/60 dark:bg-white/2 dark:ring-white/10',
bar: 'bg-zinc-400 dark:bg-zinc-500',
},
neutral: {
background: 'bg-neutral-950/2.5 dark:bg-white/2.5',
icon: 'text-neutral-500 dark:text-neutral-400',
text: 'text-neutral-600 dark:text-neutral-400',
barBackground: 'bg-neutral-50/60 ring-neutral-200/60 dark:bg-white/2 dark:ring-white/10',
bar: 'bg-neutral-400 dark:bg-neutral-500',
},
stone: {
background: 'bg-stone-950/2.5 dark:bg-white/2.5',
icon: 'text-stone-500 dark:text-stone-400',
text: 'text-stone-600 dark:text-stone-400',
barBackground: 'bg-stone-50/60 ring-stone-200/60 dark:bg-white/2 dark:ring-white/10',
bar: 'bg-stone-400 dark:bg-stone-500',
},
mauve: {
background: 'bg-mauve-950/2.5 dark:bg-white/2.5',
icon: 'text-mauve-500 dark:text-mauve-400',
text: 'text-mauve-600 dark:text-mauve-400',
barBackground: 'bg-mauve-50/60 ring-mauve-200/60 dark:bg-white/2 dark:ring-white/10',
bar: 'bg-mauve-400 dark:bg-mauve-500',
},
olive: {
background: 'bg-olive-950/2.5 dark:bg-white/2.5',
icon: 'text-olive-500 dark:text-olive-400',
text: 'text-olive-600 dark:text-olive-400',
barBackground: 'bg-olive-50/60 ring-olive-200/60 dark:bg-white/2 dark:ring-white/10',
bar: 'bg-olive-400 dark:bg-olive-500',
},
mist: {
background: 'bg-mist-950/2.5 dark:bg-white/2.5',
icon: 'text-mist-500 dark:text-mist-400',
text: 'text-mist-600 dark:text-mist-400',
barBackground: 'bg-mist-50/60 ring-mist-200/60 dark:bg-white/2 dark:ring-white/10',
bar: 'bg-mist-400 dark:bg-mist-500',
},
taupe: {
background: 'bg-taupe-950/2.5 dark:bg-white/2.5',
icon: 'text-taupe-500 dark:text-taupe-400',
text: 'text-taupe-600 dark:text-taupe-400',
barBackground: 'bg-taupe-50/60 ring-taupe-200/60 dark:bg-white/2 dark:ring-white/10',
bar: 'bg-taupe-400 dark:bg-taupe-500',
},
} satisfies Record<string, NoticeColors>
const sizes = {
md: 'rounded-lg px-3.5 py-3',
lg: 'rounded-xl px-4 py-3.5',
}
const text = 'text-base/6 sm:text-sm/6'
const props = withDefaults(
defineProps<{
tone?: keyof typeof colors
variant?: 'filled' | 'bar'
size?: keyof typeof sizes
icon?: Component
title?: string
}>(),
{ tone: 'zinc', variant: 'filled', size: 'md' }
)
const slots = useSlots()
const color = computed<NoticeColors>(() => colors[props.tone])
const titleColor = computed(() => color.value.title ?? 'text-zinc-950 dark:text-white')
const bodyColor = computed(() => color.value.body ?? 'text-zinc-600 dark:text-zinc-400')
</script>
<template>
<div v-if="variant === 'bar'" :class="['relative rounded-xl py-4 pr-5 pl-6 ring-1', color.barBackground]">
<span aria-hidden="true" :class="['absolute inset-y-4 left-3 w-0.75 rounded-full', color.bar]" />
<div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
<div class="text-sm/relaxed text-zinc-600 dark:text-zinc-300">
<p v-if="title" class="font-semibold text-zinc-950 dark:text-white">{{ title }}</p>
<div v-if="slots.default" :class="title && 'mt-1'">
<slot />
</div>
</div>
<div v-if="slots.action" class="flex shrink-0 items-center gap-2 sm:justify-end">
<slot name="action" />
</div>
</div>
</div>
<div
v-else-if="slots.action"
:class="['flex flex-wrap items-center gap-x-4 gap-y-3', sizes[size], color.background]"
>
<component :is="icon" v-if="icon" :class="['size-4 h-lh shrink-0', color.icon]" />
<div :class="['min-w-0 flex-1 text-pretty', text, color.text]">
<p v-if="title" :class="['font-medium', titleColor]">{{ title }}</p>
<slot />
</div>
<div class="flex shrink-0 items-center gap-3 max-sm:w-full">
<slot name="action" />
</div>
</div>
<div
v-else-if="title"
:class="[icon ? 'grid grid-cols-[--spacing(4)_1fr] gap-x-2.5 gap-y-1' : 'flex flex-col gap-y-1', sizes[size], color.background]"
>
<component :is="icon" v-if="icon" :class="['row-start-1 size-4 shrink-0 self-center', color.icon]" />
<p :class="['font-medium', text, titleColor]">{{ title }}</p>
<div v-if="slots.default" :class="[icon && 'col-start-2', 'text-pretty', text, bodyColor]">
<slot />
</div>
</div>
<div v-else :class="['flex items-start gap-2.5', sizes[size], color.plainBackground ?? color.background]">
<component :is="icon" v-if="icon" :class="['size-4 h-lh shrink-0', color.plainIcon ?? color.icon]" />
<div :class="['min-w-0 text-pretty', text, color.text]">
<slot />
</div>
</div>
</template>Component API
| Prop | Default | Description |
|---|---|---|
Noticerenders a <div> element | ||
tone | zinc | Any Tailwind color, from `red` to `taupe`. `zinc` for information, `amber` for a warning, `red` for a failure, `green` for something that succeeded. |
variant | filled | `filled` for a tinted well with an icon, `bar` for a pale block marked by a colored bar on the left. |
size | md | `md` inside forms and dialogs, `lg` for the notices of a detail page or a wizard. Not used with `variant="bar"`. |
icon | - | A 16px Heroicon drawn before the text, tinted by the tone. Not shown with `variant="bar"`. |
title | - | First line in medium weight. The body then sits under it, aligned with it. |
action | - | Rendered opposite the text, a button. Wraps under the text on small screens. |
| Prop | Default | Description |
|---|---|---|
Noticerenders a <div> element | ||
tone | zinc | Any Tailwind color, from `red` to `taupe`. `zinc` for information, `amber` for a warning, `red` for a failure, `green` for something that succeeded. |
variant | filled | `filled` for a tinted well with an icon, `bar` for a pale block marked by a colored bar on the left. |
size | md | `md` inside forms and dialogs, `lg` for the notices of a detail page or a wizard. |
icon | - | A 16px Heroicon drawn before the text, tinted by the tone. Not shown with `variant="bar"`. |
title | - | First line in medium weight. The body then sits under it, aligned with it. |
#action | - | Slot rendered opposite the text, a button. Wraps under the text on small screens. |
Examples
Basic example
The default tone reads as information: a grey well and a grey icon. Emphasize a value inline with font-medium:
import { Notice } from '@/components/notice'
import { EnvelopeIcon } from '@heroicons/react/16/solid'
export default function Example() {
return (
<Notice icon={EnvelopeIcon}>
We will send a verification code to <strong className="font-medium text-zinc-950 dark:text-white">l•••@example.com</strong>.
</Notice>
)
}<script setup lang="ts">
import Notice from '@/components/notice.vue'
import { EnvelopeIcon } from '@heroicons/vue/16/solid'
</script>
<template>
<Notice :icon="EnvelopeIcon">
We will send a verification code to
<strong class="font-medium text-zinc-950 dark:text-white">l•••@example.com</strong>.
</Notice>
</template>With a title
Add a title when the notice explains a choice. The icon aligns with the title and the body sits under it:
Secret key
<Notice icon={KeyIcon} title="Secret key">
Full access. Keep secret and use server-side only.
</Notice><template>
<Notice :icon="KeyIcon" title="Secret key">
Full access. Keep secret and use server-side only.
</Notice>
</template>Tones
An untitled amber notice turns fully amber; a titled one keeps its text dark under an amber well. Red is for a failure, green for a step that succeeded:
Payouts are stopped
This customer is blocked and cannot pay.
passport.pdf
<Notice tone="amber" icon={ExclamationTriangleIcon}>
Your upgrade to Scale is waiting for payment confirmation.
</Notice>
<Notice tone="amber" icon={ExclamationTriangleIcon} title="Payouts are stopped">
API calls to create a transfer will be refused. Transfers already in flight are not affected.
</Notice>
<Notice tone="red" icon={ExclamationTriangleIcon} title="This customer is blocked and cannot pay.">
Chargeback pattern on three payments. Blocked on Aug 13, 2026.
</Notice>
<Notice tone="green" icon={CheckCircleIcon} title="passport.pdf">
Uploaded a minute ago.
</Notice><template>
<Notice tone="amber" :icon="ExclamationTriangleIcon">
Your upgrade to Scale is waiting for payment confirmation.
</Notice>
<Notice tone="amber" :icon="ExclamationTriangleIcon" title="Payouts are stopped">
API calls to create a transfer will be refused. Transfers already in flight are not affected.
</Notice>
<Notice tone="red" :icon="ExclamationTriangleIcon" title="This customer is blocked and cannot pay.">
Chargeback pattern on three payments. Blocked on Aug 13, 2026.
</Notice>
<Notice tone="green" :icon="CheckCircleIcon" title="passport.pdf">
Uploaded a minute ago.
</Notice>
</template>Colors
tone accepts every color of the Tailwind palette, in both variants:
Invitation sent
<Notice tone="blue" icon={InformationCircleIcon}>
Your trial ends in 5 days.
</Notice>
<Notice tone="violet" icon={SparklesIcon}>
Dark mode is now available in your settings.
</Notice>
<Notice variant="bar" tone="teal" title="Invitation sent">
Your teammate will receive an email with a link to join the workspace.
</Notice><template>
<Notice tone="blue" :icon="InformationCircleIcon">
Your trial ends in 5 days.
</Notice>
<Notice tone="violet" :icon="SparklesIcon">
Dark mode is now available in your settings.
</Notice>
<Notice variant="bar" tone="teal" title="Invitation sent">
Your teammate will receive an email with a link to join the workspace.
</Notice>
</template>With an action
The action slot puts a button opposite the text. Below sm it wraps to its own line at full width:
<Notice icon={ChartBarIcon} action={<Button outline>Switch to live</Button>}>
Payment analytics are computed on live traffic only. Sandbox payments are not indexed.
</Notice><template>
<Notice :icon="ChartBarIcon">
Payment analytics are computed on live traffic only. Sandbox payments are not indexed.
<template #action>
<Button outline>Switch to live</Button>
</template>
</Notice>
</template>Large
size="lg" gives the notice the radius and padding of a detail page block, next to cards and tables:
Identity is checked before saving
<Notice size="lg" icon={ShieldCheckIcon} title="Identity is checked before saving">
Your password and second factor are asked once you confirm. The account stays
unverified until our team validates it.
</Notice><template>
<Notice size="lg" :icon="ShieldCheckIcon" title="Identity is checked before saving">
Your password and second factor are asked once you confirm. The account stays
unverified until our team validates it.
</Notice>
</template>Bar
variant="bar" draws a pale block with a colored bar on the left, for a note that sits in the page longer than a warning. It takes a title, a body and an action, without an icon:
Scheduled maintenance
New export format
<Notice variant="bar" tone="amber" title="Scheduled maintenance">
The app will be unavailable on Sunday from 2:00 to 4:00 AM UTC. Save your work before then.
</Notice>
<Notice variant="bar" tone="blue" title="New export format" action={<Button outline>Try it</Button>}>
Reports can now be exported as CSV or XLSX.
</Notice><template>
<Notice variant="bar" tone="amber" title="Scheduled maintenance">
The app will be unavailable on Sunday from 2:00 to 4:00 AM UTC. Save your work before then.
</Notice>
<Notice variant="bar" tone="blue" title="New export format">
Reports can now be exported as CSV or XLSX.
<template #action>
<Button outline>Try it</Button>
</template>
</Notice>
</template>