Feedback
Note
Inline callout for contextual feedback, styled with the quebi design system. Five intents with optional status icon and heading.
alertcalloutfeedbacknoticebanner
Intents
Five intents — neutral default plus info, success, warning and danger.
Heads up — this plan is still a draft and isn't live on any kiosk yet.
Pricing for this carrier is managed centrally and syncs automatically.
Changes autosaved · 14:02. Your kiosk will pick them up within a minute.
Two devices in this bundle are out of stock — customers can't complete checkout.
We couldn't save this price — check your connection and try again.
With title
A bold heading sits above the body content.
Centrally managed
Carriers, devices and plans flow from the catalog. Edits here apply to your storefront only.
Couldn't publish
Three plans failed validation. Fix the highlighted prices and publish again.
Without indicator
Drop the leading status icon for a quieter inline note.
Changes autosaved · 14:02.
This device is system-managed and can't be edited.
Rich content
Bodies accept inline markup like links and emphasis.
Action needed
VAT rate changed. Review affected plans in billing settings before your next publish.
Source
Copy this into your project. Resolve its dependencies from the registryDependencies in the component's API entry.
import { tv } from "tailwind-variants"
import { cn } from "@/lib/utils"
type IconProps = React.SVGProps<SVGSVGElement>
const baseIconProps: IconProps = {
viewBox: "0 0 24 24",
fill: "currentColor",
"aria-hidden": "true",
}
const InformationCircleIcon = (props: IconProps) => (
<svg {...baseIconProps} {...props}>
<path
fillRule="evenodd"
d="M2.25 12a9.75 9.75 0 1 1 19.5 0 9.75 9.75 0 0 1-19.5 0Zm9-1.5a.75.75 0 0 0 0 1.5h.255a.75.75 0 0 1 .73.926l-.708 2.836A1.75 1.75 0 0 0 13.225 18h.526a.75.75 0 0 0 0-1.5h-.255a.25.25 0 0 1-.243-.31l.71-2.836A1.75 1.75 0 0 0 12.265 11H11.25Zm.75-3.75a1.125 1.125 0 1 0 0 2.25 1.125 1.125 0 0 0 0-2.25Z"
clipRule="evenodd"
/>
</svg>
)
const CheckCircleIcon = (props: IconProps) => (
<svg {...baseIconProps} {...props}>
<path
fillRule="evenodd"
d="M2.25 12a9.75 9.75 0 1 1 19.5 0 9.75 9.75 0 0 1-19.5 0Zm13.36-1.814a.75.75 0 1 0-1.22-.872l-3.236 4.53L9.53 12.22a.75.75 0 0 0-1.06 1.06l2.25 2.25a.75.75 0 0 0 1.14-.094l3.75-5.25Z"
clipRule="evenodd"
/>
</svg>
)
const ExclamationTriangleIcon = (props: IconProps) => (
<svg {...baseIconProps} {...props}>
<path
fillRule="evenodd"
d="M9.401 3.003c1.155-2 4.043-2 5.197 0l7.355 12.748c1.154 2-.29 4.5-2.599 4.5H4.645c-2.309 0-3.752-2.5-2.598-4.5L9.4 3.003ZM12 8.25a.75.75 0 0 1 .75.75v3.75a.75.75 0 0 1-1.5 0V9a.75.75 0 0 1 .75-.75Zm0 8.25a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5Z"
clipRule="evenodd"
/>
</svg>
)
const XCircleIcon = (props: IconProps) => (
<svg {...baseIconProps} {...props}>
<path
fillRule="evenodd"
d="M12 2.25c-5.385 0-9.75 4.365-9.75 9.75s4.365 9.75 9.75 9.75 9.75-4.365 9.75-9.75S17.385 2.25 12 2.25Zm-1.72 6.97a.75.75 0 1 0-1.06 1.06L10.94 12l-1.72 1.72a.75.75 0 1 0 1.06 1.06L12 13.06l1.72 1.72a.75.75 0 1 0 1.06-1.06L13.06 12l1.72-1.72a.75.75 0 1 0-1.06-1.06L12 10.94l-1.72-1.72Z"
clipRule="evenodd"
/>
</svg>
)
/**
* Note — quebi design system
*
* An inline callout / alert for contextual feedback. Five intents:
* default (neutral surface), info (cyan), success (emerald), warning
* (amber), danger (red). Borders use the signature translucent rings;
* each intent tints its surface and leading status icon to match.
*/
export interface NoteProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "title"> {
intent?: "default" | "info" | "warning" | "danger" | "success"
/** Show the leading status icon. Defaults to true (the `default` intent has no icon). */
indicator?: boolean
/** Optional bold heading rendered above the body content. */
title?: React.ReactNode
}
const noteStyles = tv({
base: [
"flex w-full gap-3 rounded-quebi-md border p-4 text-sm/5 text-pretty",
"*:[a]:font-medium *:[a]:underline *:[a]:hover:no-underline",
"**:[strong]:font-medium **:[.text-muted-fg]:text-quebi-fg-muted",
],
variants: {
intent: {
default: "border-cyan-500/10 bg-white/[0.03] text-quebi-fg-muted",
info: "border-cyan-500/20 bg-cyan-500/10 text-cyan-200",
success: "border-emerald-500/20 bg-emerald-500/10 text-emerald-200",
warning: "border-amber-500/20 bg-amber-500/10 text-amber-200",
danger: "border-red-500/20 bg-red-500/10 text-red-200",
},
},
defaultVariants: { intent: "default" },
})
const iconMap = {
info: InformationCircleIcon,
success: CheckCircleIcon,
warning: ExclamationTriangleIcon,
danger: XCircleIcon,
default: null,
} as const
export function Note({
indicator = true,
intent = "default",
title,
className,
children,
...props
}: NoteProps) {
const Icon = iconMap[intent]
return (
<div data-slot="note" className={cn(noteStyles({ intent }), className)} {...props}>
{Icon && indicator && <Icon aria-hidden="true" className="mt-px size-5 shrink-0" />}
<div className="min-w-0 flex-1">
{title && <div className="font-semibold text-white">{title}</div>}
<div className={title ? "mt-1" : undefined}>{children}</div>
</div>
</div>
)
}