Disclaimer banner

Slim supplement disclaimer band with an expandable full statement.

componentbanneranydisclaimer
$npx shadcn@latest add @storekit/disclaimer-banner
disclaimer-banner

Live and interactive. Switch the brand in the frame header to see the same source under another token file.

Installation

$npx shadcn@latest add @storekit/disclaimer-banner
$pnpm dlx shadcn@latest add @storekit/disclaimer-banner
$yarn dlx shadcn@latest add @storekit/disclaimer-banner
$bunx --bun shadcn@latest add @storekit/disclaimer-banner

Source

components/storekit/compliance/disclaimer-banner.tsx
58 lines
"use client"

import { useId, useState } from "react"
import { AnimatePresence, motion, useReducedMotion } from "motion/react"
import { ChevronDown, Info } from "lucide-react"
import { cn } from "@/components/storekit/lib/cn"

/* Slim bordered disclaimer band. "Read more" expands the full statement
   in place; the summary stays visible so the disclaimer is never hidden. */

export function DisclaimerBanner({
  summary = "These statements have not been evaluated by the FDA.",
  details = "These statements have not been evaluated by the Food and Drug Administration. Our products are dietary supplements and are not intended to diagnose, treat, cure, or prevent any disease. Talk to your doctor before use if you are pregnant, nursing, taking medication or have a medical condition. Keep out of reach of children.",
  className,
}: {
  summary?: string
  details?: string
  className?: string
}) {
  const reduce = useReducedMotion()
  const [open, setOpen] = useState(false)
  const id = useId()
  return (
    <aside aria-label="Disclaimer" data-sk-section="compliance" data-sk-variant="disclaimer-banner" className={cn("rounded-sk-card border border-sk-line bg-sk-surface text-sk-ink", className)}>
      <div className="flex min-h-11 items-start gap-3 py-1 pl-4 pr-1 sm:items-center">
        <Info size={16} aria-hidden className="mt-3 shrink-0 text-sk-muted sm:mt-0" />
        <div className="flex min-w-0 flex-1 flex-col sm:flex-row sm:items-center sm:gap-3">
          <p className="flex-1 pt-2.5 text-[13px] font-medium leading-[1.5] sm:py-2.5">{summary}</p>
          <button
            type="button"
            aria-expanded={open}
            aria-controls={id}
            onClick={() => setOpen((o) => !o)}
            className="-ml-3 inline-flex h-11 shrink-0 items-center gap-1 self-start rounded-sk-btn px-3 text-[13px] text-sk-muted outline-none transition-colors duration-200 hover:text-sk-ink focus-visible:ring-2 focus-visible:ring-sk-accent focus-visible:ring-offset-2 focus-visible:ring-offset-sk-surface sm:ml-0 sm:self-auto"
          >
            {open ? "Show less" : "Read more"}
            <ChevronDown size={14} aria-hidden className={cn("transition-transform duration-200", open && "rotate-180")} />
          </button>
        </div>
      </div>
      <AnimatePresence initial={false}>
        {open ? (
          <motion.div
            id={id}
            initial={reduce ? { opacity: 0 } : { height: 0, opacity: 0 }}
            animate={reduce ? { opacity: 1 } : { height: "auto", opacity: 1 }}
            exit={reduce ? { opacity: 0 } : { height: 0, opacity: 0 }}
            transition={{ duration: 0.35, ease: [0.16, 1, 0.3, 1] }}
            className="overflow-hidden"
          >
            <p className="border-t border-sk-line px-4 py-3 pl-11 text-[13px] leading-[1.5] text-sk-muted">{details}</p>
          </motion.div>
        ) : null}
      </AnimatePresence>
    </aside>
  )
}

Usage

Import from "@/components/storekit/compliance/disclaimer-banner" and pass your product data. The demo above exercises the full prop surface with sample values.