Skip to main content
Clever Ops

Guardrail Notice

An inline policy interjection, a quiet card explaining why the agent modified or declined an action, with an expandable mono policy reference; honest tone, destructive keyline only when blocked.

Preview & code

Loading Guardrail Notice preview

Installation

pnpm dlx shadcn@latest add @cleverui/guardrail-notice

Requires the @cleverui registry in your components.json, a one-off, two-line setup.

Usage

tsx
import { GuardrailNotice } from '@/components/ui/guardrail-notice'
Exampletsx
'use client'

import { useEffect, useState } from 'react'
import { useReducedMotion } from 'motion/react'
import {
  GuardrailNotice,
  type GuardrailKind,
} from '@/components/ui/guardrail-notice'

type Frame = {
  kind: GuardrailKind
  title: string
  explanation: string
  policy: string
  holdMs: number
}

const FRAMES: Frame[] = [
  {
    kind: 'modified',
    title: 'Refund capped at $500',
    explanation:
      'The agent proposed a $1,240 refund for Acme Logistics. Policy finance.refunds.v3 rewrote the amount to $500 and queued the remainder for controller approval.',
    policy:
      'finance.refunds.v3, refunds above $500 require controller approval; agent may auto-execute only up to the cap',
    holdMs: 4200,
  },
  {
    kind: 'declined',
    title: 'Bulk contact export declined',
    explanation:
      'Export of 14,200 customer emails was refused. Privacy gate requires a ticketed legal hold request before any full-book CSV leaves the workspace.',
    policy:
      'privacy.export.v2, bulk contact exports require legal_hold ticket + DPO sign-off; agents may not initiate unattended exports',
    holdMs: 4200,
  },
  {
    kind: 'blocked',
    title: 'Credential rotation blocked',
    explanation:
      'Attempted rotation of the Xero production OAuth client was stopped. Secrets changes outside the change window are hard-blocked until Monday 09:00 UTC.',
    policy:
      'security.secrets.v4, production credential rotation blocked outside change window (Mon 09:00–Thu 18:00 UTC); emergency override requires on-call approver',
    holdMs: 4200,
  },
]

export default function GuardrailNoticeDemo() {
  const reduced = useReducedMotion()
  const [index, setIndex] = useState(0)
  const [dismissed, setDismissed] = useState(false)
  const frame = FRAMES[index] ?? FRAMES[0]!

  useEffect(() => {
    if (reduced) {
      setIndex(0)
      return
    }
    if (dismissed) return
    const id = window.setTimeout(() => {
      setIndex((i) => (i + 1) % FRAMES.length)
    }, frame.holdMs)
    return () => window.clearTimeout(id)
  }, [index, frame.holdMs, reduced, dismissed])

  return (
    <div className="mx-auto flex min-h-[400px] w-full max-w-lg flex-col justify-center px-4 py-12 sm:px-6 sm:py-16">
      <div className="space-y-4 rounded-2xl border border-border/60 bg-card p-4 sm:p-5">
        <p className="font-mono text-[11px] uppercase tracking-[0.2em] text-muted-foreground sm:text-xs">
          meridian-ops · message stream
        </p>
        <p className="text-sm leading-relaxed text-foreground">
          I started the Acme Logistics refund for invoice inv_2026_03_8841.
        </p>
        {!dismissed ? (
          <GuardrailNotice
            key={`${frame.kind}-${index}`}
            kind={frame.kind}
            title={frame.title}
            explanation={frame.explanation}
            policy={frame.policy}
            onDismiss={() => setDismissed(true)}
          />
        ) : (
          <button
            type="button"
            onClick={() => {
              setDismissed(false)
              setIndex(0)
            }}
            className="self-start font-mono text-[11px] uppercase tracking-[0.2em] text-muted-foreground underline-offset-4 transition-colors hover:text-foreground hover:underline focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background focus-visible:outline-hidden"
          >
            Show notice again
          </button>
        )}
        <p className="text-sm leading-relaxed text-muted-foreground">
          Reply with approve if the rewritten amount looks right, or open the
          controller queue for the remainder.
        </p>
      </div>
    </div>
  )
}

API reference

GuardrailNotice accepts the following props.

PropTypeDefaultDescription
title*stringplain-language headline, e.g. 'Refund capped at $500'
explanation*stringone or two muted sentences on what changed and why
kindGuardrailKind'modified'register of the interjection; only blocked engages the destructive keyline
policystringpolicy reference rendered verbatim in the expandable mono block
policyLabelstring'Policy reference'disclosure label for the mono block
onDismiss() => voidrenders a ghost dismiss button; omitted, the notice is permanent

…plus everything from Omit< React.HTMLAttributes<HTMLElement>, 'onDrag' | 'onDragStart' | 'onDragEnd' | 'onAnimationStart' | 'title' >: className, event handlers, aria attributes and the rest pass straight through.

Referenced types

Typestsx
export type GuardrailKind = 'modified' | 'declined' | 'blocked'

Frequently asked questions

How do I install the Guardrail Notice component?

Register the @cleverui registry in your components.json ("@cleverui": "https://cleverops.com.au/r/{name}.json"), then run `npx shadcn@latest add @cleverui/guardrail-notice`. The shadcn CLI copies the source into your project, you own the code from that point. You can also copy the source directly from this page.

What dependencies does Guardrail Notice require?

Guardrail Notice uses motion and lucide-react on top of React and Tailwind CSS. You don't need to install them manually, the shadcn CLI resolves and installs npm dependencies automatically when you add the component.

Can I theme Guardrail Notice to match my brand?

Yes. The component is styled entirely with Tailwind utilities over standard design tokens (primary, background, muted, border), so it inherits your existing shadcn/ui theme automatically. Change your CSS variables and the component follows, no source edits needed for palette, radius, or fonts.

Want this built for you?

CleverUI is built by the Clever Ops web team. If you'd rather ship a hand-coded, fast, custom website than assemble one, we build them for Australian businesses every week.

Explore web development services