Skip to main content
Clever Ops

Reasoning Disclosure

A model thinking block, collapsed to a shimmering summary line while streaming, expanding to the full reasoning text with a mono character count; settles quiet when done.

Preview & code

Loading Reasoning Disclosure preview

Installation

pnpm dlx shadcn@latest add @cleverui/reasoning-disclosure

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

Usage

tsx
import { ReasoningDisclosure } from '@/components/ui/reasoning-disclosure'
Exampletsx
'use client'

import { useEffect, useMemo, useState } from 'react'
import { useReducedMotion } from 'motion/react'
import { ReasoningDisclosure } from '@/components/ui/reasoning-disclosure'

const FULL_REASONING = `Checking whether invoice inv_2026_03_8841 already has a matching credit note in Xero.

1. The Stripe charge at 14:22 UTC is $1,240.00 to Acme Logistics, same amount as the March close pack line.
2. Xero shows two sales invoices for that customer on the same day; only one has a payment applied.
3. The open invoice carries metadata.agent = meridian-ops from the prior run, which suggests a double post rather than a new bill.
4. Customer ops ticket #4412 confirms the duplicate and asks for a full refund to the original card.
5. Risk is medium: refund is reversible via Stripe, but the ledger will need a compensating journal if we deny.

Recommendation: propose stripe.refunds.create for $1,240 and pause for human approval before posting.`

/** Deterministic chunk schedule, no Math.random */
const CHUNK_ENDS = [80, 180, 320, 480, 640, 820, FULL_REASONING.length]

export default function ReasoningDisclosureDemo() {
  const reduced = useReducedMotion()
  const [charIndex, setCharIndex] = useState(0)
  const [streaming, setStreaming] = useState(true)
  const [step, setStep] = useState(0)

  const text = useMemo(
    () => FULL_REASONING.slice(0, charIndex),
    [charIndex]
  )

  useEffect(() => {
    if (reduced) {
      setCharIndex(FULL_REASONING.length)
      setStreaming(false)
      return
    }

    if (step >= CHUNK_ENDS.length) {
      setStreaming(false)
      return
    }

    const target = CHUNK_ENDS[step] ?? FULL_REASONING.length
    const id = window.setTimeout(() => {
      setCharIndex(target)
      setStep((s) => s + 1)
    }, step === 0 ? 400 : 700)

    return () => window.clearTimeout(id)
  }, [step, reduced])

  return (
    <div className="mx-auto flex min-h-[440px] w-full max-w-2xl flex-col justify-center px-4 py-12 sm:px-6 sm:py-16">
      <div className="rounded-2xl border border-border/60 bg-card p-4 sm:p-5">
        <p className="mb-4 font-mono text-[11px] uppercase tracking-[0.2em] text-muted-foreground sm:text-xs">
          meridian-ops · message
        </p>
        <ReasoningDisclosure
          text={text}
          streaming={streaming}
          defaultOpen
          label="Thinking"
          doneLabel="Thought process"
        />
        <div className="mt-4 border-t border-border/60 pt-4">
          <p className="text-sm leading-relaxed text-foreground">
            {streaming
              ? 'Working through the Acme Logistics duplicate charge…'
              : 'I recommend a $1,240 refund to Acme Logistics for invoice inv_2026_03_8841, pending your approval.'}
          </p>
        </div>
      </div>
    </div>
  )
}

API reference

ReasoningDisclosure accepts the following props.

PropTypeDefaultDescription
text*stringreasoning text streamed so far, parent appends; component only renders
streamingbooleanfalsetrue while tokens are arriving; drives shimmer and live keyline
labelstring'Thinking'collapsed summary line while streaming / idle open
doneLabelstring'Thought process'quiet label once streaming is false
countnumbertoken count override; defaults to text.length characters
defaultOpenbooleanfalse
onToggle(open: boolean) => void

…plus everything from Omit<React.HTMLAttributes<HTMLDivElement>, 'onToggle'>: className, event handlers, aria attributes and the rest pass straight through.

Frequently asked questions

How do I install the Reasoning Disclosure component?

Register the @cleverui registry in your components.json ("@cleverui": "https://cleverops.com.au/r/{name}.json"), then run `npx shadcn@latest add @cleverui/reasoning-disclosure`. 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 Reasoning Disclosure require?

Reasoning Disclosure 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 Reasoning Disclosure 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