Components
Info note
Inline contextual message. Five intents (default, info, success, warning, error) across two sizes. Pill rounded, leading icon, optional trailing action.
Preview#
Something you might like to know.
Saved successfully.
Heads up — re-check your settings.
Failed to save. Try again.
<ForenInfoNote intent="info">Something you might like to know.</ForenInfoNote>
<ForenInfoNote intent="success">Saved successfully.</ForenInfoNote>
<ForenInfoNote intent="warning">Heads up — re-check your settings.</ForenInfoNote>
<ForenInfoNote intent="error">Failed to save. Try again.</ForenInfoNote>Installation#
Same package as the rest of Foren.
bash
npm install @foren/uitsx
import { ForenInfoNote } from "@foren/ui";Usage#
Drop in inline anywhere a single-line hint fits.
tsx
<ForenInfoNote intent="info">
Your draft was auto-saved a moment ago.
</ForenInfoNote>
<ForenInfoNote intent="error" action={<button>Retry</button>}>
Couldn't load tokens.
</ForenInfoNote>Source#
Drop-in source if you'd rather skip the package.
components/ui/foren-info-note.tsx
"use client";
import * as React from "react";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";
const noteVariants = cva(
"inline-flex items-center gap-2 rounded-full font-medium tracking-[0.01em] [&_svg]:shrink-0 [&_svg]:pointer-events-none w-full",
{
variants: {
intent: {
default: "bg-[var(--color-foren-bg-subtle)] text-[var(--color-foren-text-secondary)]",
info: "bg-[var(--color-foren-bg-info-subtle)] text-[var(--color-foren-info)]",
success: "bg-[var(--color-foren-bg-success-subtle)] text-[var(--color-foren-success)]",
warning: "bg-[var(--color-foren-bg-warning-subtle)] text-[var(--color-foren-warning)]",
error: "bg-[var(--color-foren-bg-error-subtle)] text-[var(--color-foren-error)]",
},
size: {
sm: "h-9 px-3 text-[13px] leading-[18px] [&_svg]:size-[14px]",
lg: "h-12 px-4 text-[14px] leading-[21px] [&_svg]:size-[18px]",
},
},
defaultVariants: { intent: "default", size: "sm" },
}
);
const InfoIcon = () => (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round">
<circle cx="12" cy="12" r="10" />
<path d="M12 16v-4" />
<path d="M12 8h.01" />
</svg>
);
const CheckIcon = () => (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round">
<circle cx="12" cy="12" r="10" />
<path d="m9 12 2 2 4-4" />
</svg>
);
const WarningIcon = () => (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round">
<circle cx="12" cy="12" r="10" />
<path d="M12 8v4" />
<path d="M12 16h.01" />
</svg>
);
const ErrorIcon = () => (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round">
<circle cx="12" cy="12" r="10" />
<path d="m15 9-6 6" />
<path d="m9 9 6 6" />
</svg>
);
const defaultIcon: Record<NonNullable<VariantProps<typeof noteVariants>["intent"]>, React.ReactNode> = {
default: <InfoIcon />,
info: <InfoIcon />,
success: <CheckIcon />,
warning: <WarningIcon />,
error: <ErrorIcon />,
};
export interface ForenInfoNoteProps
extends Omit<React.HTMLAttributes<HTMLDivElement>, "children">,
VariantProps<typeof noteVariants> {
/** Body text. */
children: React.ReactNode;
/** Override the default leading icon (per intent). Pass `null` to hide. */
icon?: React.ReactNode | null;
/** Optional trailing slot — for a dismiss button or link. */
action?: React.ReactNode;
}
export const ForenInfoNote = React.forwardRef<HTMLDivElement, ForenInfoNoteProps>(
function ForenInfoNote(
{ className, intent = "default", size = "sm", icon, action, children, ...props },
ref
) {
const resolvedIcon = icon === null ? null : icon ?? defaultIcon[intent ?? "default"];
const ariaLive = intent === "error" || intent === "warning" ? "assertive" : "polite";
const role = intent === "error" ? "alert" : "status";
return (
<div
ref={ref}
role={role}
aria-live={ariaLive}
className={cn(noteVariants({ intent, size }), className)}
{...props}
>
{resolvedIcon}
<span className="flex-1 min-w-0 truncate">{children}</span>
{action}
</div>
);
}
);
export { noteVariants };
Intents#
Five semantic intents — each gets its own bg + text + icon.
Default — something you might like to know.
Info — something you might like to know.
Success — something you might like to know.
Warning — something you might like to know.
Error — something you might like to know.
<ForenInfoNote intent="default">…</ForenInfoNote>
<ForenInfoNote intent="info">…</ForenInfoNote>
<ForenInfoNote intent="success">…</ForenInfoNote>
<ForenInfoNote intent="warning">…</ForenInfoNote>
<ForenInfoNote intent="error">…</ForenInfoNote>Sizes#
Two sizes — sm 36px (default), lg 48px.
Size sm — something you might like to know.
Size lg — something you might like to know.
<ForenInfoNote intent="info" size="sm">…</ForenInfoNote>
<ForenInfoNote intent="info" size="lg">…</ForenInfoNote>Without icon#
Hide the leading icon by passing icon={null}.
Plain message — no icon.
Saved.
<ForenInfoNote intent="default" icon={null}>Plain message — no icon.</ForenInfoNote>With action#
Add a trailing action — link, button, or chip.
Couldn't reach the server.
New tokens were synced from Figma.Learn more
<ForenInfoNote
intent="error"
action={<button onClick={retry}>Retry</button>}
>
Couldn't reach the server.
</ForenInfoNote>Show as toast#
Imperative API — show notes from anywhere via useInfoNote(). Slides in, auto-dismisses, stackable.
// Root layout — wrap once
import { InfoNoteProvider } from "@foren/ui";
<InfoNoteProvider position="top-right">{children}</InfoNoteProvider>
// Anywhere in the tree
const note = useInfoNote();
note.info("Tokens synced.");
note.success("Saved.");
note.warning("Re-check your settings.");
note.error("Failed. Try again.");
// Sticky note — dismiss to close
note.show({ intent: "info", message: "Pinned.", duration: 0 });
// Custom duration + action
note.show({
intent: "success",
message: "Invite sent.",
duration: 4000,
action: <button onClick={undo}>Undo</button>,
});API#
PropTypeDefault
intent"default" | "info" | "success" | "warning" | "error"
size"sm" | "lg"
iconReactNode | null
actionReactNode
childrenReactNode (body)
…restHTMLAttributes<HTMLDivElement>