Skip to content
UI Kit
Accent color

Money back guarantee

Guarantees that take the risk off the buyer: a large seal with the days, a scale that tips while scrolling, the steps of a refund, several promises with their small print and a compact row for next to the buy button.

5 variantsOne of 2 components in Security and trust

Take the risk off the buyer

A guarantee answers the last doubt before a purchase, what if it does not work for me. These five sections show the promise clearly and keep the conditions honest and close, so the guarantee builds trust instead of raising new questions.

Seal with conditions draws a large seal whose rim turns with the scroll and whose days count up, the promise and what it covers beside it and the conditions behind a disclosure. Risk on our side tips a drawn scale while you scroll and lists what the buyer carries and what the company carries. How a return works puts the days of a return on a track that fills while scrolling, down the page on phones and across it on wider screens, the last step is the money back. Promises with small print sets several guarantees in a hairline grid, each with a figure, a short promise and its small print opened in place. Next to the buy button is a compact card with a ring around the days and three short promises.

Write the guarantee as one sentence a customer can repeat. Keep the small print short and readable, conditions that hide cost more trust than they save. Repeat the compact version next to the price, that is where the doubt appears.

Variants

Seal with conditionsNew in 1.2

A large drawn seal whose rim turns with the scroll and whose days count up, the promise and what it covers beside it, the conditions behind a disclosure.

Risk on our sideNew in 1.2

A drawn scale tips while you scroll and puts the weight on the seller. Below it two cards list what the buyer carries and what the company carries.

How a return worksNew in 1.2

The days of a return on a track that fills while scrolling. Down the page on phones, across it on wider screens, the last step is the money back.

Promises with small printNew in 1.2

Several guarantees in a hairline grid, each with a figure that counts up, a short promise and its small print, opened in place.

Next to the buy buttonNew in 1.2

A compact card with a ring that closes around the days and three short promises, made to sit right under a price and a buy button.

Details

What Guarantee brings along, read from the registry and the source.

Variants
5
Version
1.0.0 · since 1.2.0
File
components/ui/guarantee.tsx
Brings along
utilskitmotionbuttoniconnumber-tickersection
npm packages
None
Languages
English and German, 8 texts of its own
Studio
Adjust in code

Props

The GuaranteeProps interface from components/ui/guarantee.tsx, with the comments from the code.

  • variantoptional
    GuaranteeVariant

    sealbalancerefund-stepspromisesinline

  • eyebrowoptional
    React.ReactNode
  • titleoptional
    React.ReactNode
  • descriptionoptional
    React.ReactNode
  • daysoptional
    number

    seal, inline: number of days

  • rimoptional
    string

    seal: text on the rim

  • pointsoptional
    GuaranteePoint[]

    seal, inline: what is promised, short

  • fineoptional
    string

    seal: the conditions in full

  • riskoptional
    { yoursLabel: string; yours: string[]; oursLabel: string; ours: string[] }

    balance: what each side carries

  • stepsoptional
    { day: string; title: string; text?: string; icon?: IconName }[]

    refund-steps: the days of a return

  • promisesoptional
    GuaranteePromise[]

    promises

  • ctaoptional
    { label: string; href: string }
  • noteoptional
    string

    A short line under the action

Built-in texts

Guarantee writes these texts itself. The language follows the KitProvider, the labels prop replaces single ones.

Show all 8 texts
daysEnglish daysGerman Tage
moneyBackEnglish Money backGerman Geld zurück
fineEnglish The small printGerman Das Kleingedruckte
hideEnglish Hide the small printGerman Kleingedrucktes ausblenden
guaranteeEnglish GuaranteeGerman Garantie
yoursEnglish You carryGerman Du trägst
oursEnglish We carryGerman Wir tragen
nothingEnglish Nothing but a few minutesGerman Nichts außer ein paar Minuten

Accessibility

Read from the source of the component.

  • Sets aria-controls and aria-expanded, so screen readers announce purpose and state.
  • Shows a visible focus ring when used with the keyboard.
  • Needs no client state. The server writes it in full, so it reads without JavaScript too.

Motion

  • Rises in briefly the first time it comes into view (kit-rise, useInView).
  • With reduced motion (prefers-reduced-motion) it stays still. 6 places in the code switch motion off then.

Source

The file aiw add puts into your project. From then on it is yours.

Show the file, 410 lines
components/ui/guarantee.tsx
'use client';

import { useId, useState } from 'react';
import { cn, round, useInView, useTexts } from '@/lib/kit';
import { Magnetic, useScrollProgress } from '@/components/ui/motion';
import { Button } from '@/components/ui/button';
import { Icon, type IconName } from '@/components/ui/icon';
import { NumberTicker } from '@/components/ui/number-ticker';
import { Eyebrow, Section } from '@/components/ui/section';

/*
 * A guarantee that takes the risk off the buyer, said plainly and with the
 * conditions in view. Five layouts:
 *
 *   seal          a large drawn seal with the days, the promise and its conditions
 *   balance       a scale that tips while scrolling: what you carry, what we carry
 *   refund-steps  how a return works, day by day on a track that fills
 *   promises      several guarantees with large figures and their small print
 *   inline        a compact row for next to a buy button
 */

export type GuaranteeVariant = 'seal' | 'balance' | 'refund-steps' | 'promises' | 'inline';

export interface GuaranteePoint {
  icon?: IconName;
  title: string;
  text?: string;
}

export interface GuaranteePromise {
  /** A figure that counts up, e.g. 30, or a text like 24/7 */
  value: number | string;
  /** After the figure, e.g. days or % */
  unit?: string;
  title: string;
  text?: string;
  /** Small print, opened on request */
  fine?: string;
  icon?: IconName;
}

export interface GuaranteeProps {
  variant?: GuaranteeVariant;
  eyebrow?: React.ReactNode;
  title?: React.ReactNode;
  description?: React.ReactNode;
  /** seal, inline: number of days */
  days?: number;
  /** seal: text on the rim */
  rim?: string;
  /** seal, inline: what is promised, short */
  points?: GuaranteePoint[];
  /** seal: the conditions in full */
  fine?: string;
  /** balance: what each side carries */
  risk?: { yoursLabel: string; yours: string[]; oursLabel: string; ours: string[] };
  /** refund-steps: the days of a return */
  steps?: { day: string; title: string; text?: string; icon?: IconName }[];
  /** promises */
  promises?: GuaranteePromise[];
  cta?: { label: string; href: string };
  /** A short line under the action */
  note?: string;
  labels?: Partial<Texts>;
  className?: string;
}

const TEXTS = {
  en: { days: 'days', moneyBack: 'Money back', fine: 'The small print', hide: 'Hide the small print', guarantee: 'Guarantee', yours: 'You carry', ours: 'We carry', nothing: 'Nothing but a few minutes' },
  de: { days: 'Tage', moneyBack: 'Geld zurück', fine: 'Das Kleingedruckte', hide: 'Kleingedrucktes ausblenden', guarantee: 'Garantie', yours: 'Du trägst', ours: 'Wir tragen', nothing: 'Nichts außer ein paar Minuten' },
};

type Texts = (typeof TEXTS)['en'];
type ViewProps = GuaranteeProps & { t: Texts };

const delay = (ms: number) => ({ '--kit-delay': `${ms}ms` }) as React.CSSProperties;

function Head({ eyebrow, title, description, center, className }: Pick<GuaranteeProps, 'eyebrow' | 'title' | 'description'> & { center?: boolean; className?: string }) {
  if (!title) return null;
  return (
    <div className={cn('max-w-[46rem]', center && 'mx-auto max-w-[42rem] text-center', className)}>
      {eyebrow && <Eyebrow>{eyebrow}</Eyebrow>}
      <h2 className="kit-rise text-balance text-h2 font-semibold text-foreground">{title}</h2>
      {description && (
        <p className={cn('kit-rise mt-5 max-w-[36rem] text-pretty text-lead text-muted-foreground', center && 'mx-auto')} style={delay(80)}>
          {description}
        </p>
      )}
    </div>
  );
}

function Action({ cta, note, center, className }: Pick<GuaranteeProps, 'cta' | 'note'> & { center?: boolean; className?: string }) {
  if (!cta && !note) return null;
  return (
    <div className={cn('flex flex-col gap-3 sm:flex-row sm:items-center sm:gap-5', center && 'items-center sm:justify-center', className)}>
      {cta && (
        <Magnetic className="max-sm:flex max-sm:w-full">
          <Button href={cta.href} size="lg" icon="arrow-right" className="max-sm:w-full">
            {cta.label}
          </Button>
        </Magnetic>
      )}
      {note && (
        <p className="flex items-center gap-1.5 text-[13.5px] text-muted-foreground">
          <Icon name="shield" size={14} className="shrink-0 text-success" />
          {note}
        </p>
      )}
    </div>
  );
}

/** The conditions behind a disclosure, readable without JavaScript */
function Fine({ text, t, className }: { text: string; t: Texts; className?: string }) {
  return (
    <details className={cn('group/fine', className)}>
      <summary className="inline-flex min-h-11 cursor-pointer list-none items-center gap-1.5 rounded-md text-[13.5px] font-medium text-muted-foreground transition-colors hover:text-foreground focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-solid focus-visible:outline-primary sm:min-h-8 [&::-webkit-details-marker]:hidden">
        <Icon name="chevron-right" size={15} className="transition-transform duration-300 group-open/fine:rotate-90" />
        {t.fine}
      </summary>
      <p className="mt-2 max-w-[38rem] text-pretty text-[13.5px] leading-relaxed text-muted-foreground">{text}</p>
    </details>
  );
}

/** A big round seal with the days in the middle and text on the turning rim */
function DaySeal({ days, rim, t }: { days: number; rim: string; t: Texts }) {
  const id = useId().replace(/:/g, '');
  const scroll = useScrollProgress<HTMLDivElement>({ reach: false });
  const words = rim.length > 28 ? `${rim} · ` : `${rim} · ${rim} · `;
  return (
    <div ref={scroll} className="relative mx-auto aspect-square w-full max-w-[16.25rem] lg:max-w-[21rem]">
      <div aria-hidden className="absolute inset-[8%] rounded-full bg-[radial-gradient(closest-side,var(--glow),transparent)] blur-xl" />
      <svg viewBox="0 0 200 200" aria-hidden data-clip-ok className="absolute inset-0 size-full">
        <defs>
          <path id={`${id}-r`} d="M100 100 m-78 0 a78 78 0 1 1 156 0 a78 78 0 1 1 -156 0" />
        </defs>
        <circle cx="100" cy="100" r="96" className="fill-card stroke-border" strokeWidth="1" />
        <circle cx="100" cy="100" r="88" fill="none" className="stroke-[color-mix(in_oklab,var(--primary)_45%,var(--border))]" strokeWidth="0.8" strokeDasharray="1.5 3.5" />
        <circle cx="100" cy="100" r="64" className="fill-[color-mix(in_oklab,var(--primary)_10%,var(--card))] stroke-[color-mix(in_oklab,var(--primary)_40%,var(--border))]" strokeWidth="1" />
        {/* the rim turns with the scroll, a quarter turn over the pass */}
        <g className="origin-center motion-reduce:![transform:none]" style={{ transform: 'rotate(calc(var(--kit-progress, 0.5) * 90deg - 45deg))', transformBox: 'view-box' }}>
          <text fontSize="10.5" fontWeight="600" letterSpacing="2.6" className="fill-foreground uppercase">
            <textPath href={`#${id}-r`} textLength="484" lengthAdjust="spacing">
              {words}
            </textPath>
          </text>
        </g>
      </svg>
      <div className="absolute inset-0 flex flex-col items-center justify-center text-center">
        <span className="text-[clamp(3.5rem,2.8rem+3vw,5.5rem)] font-semibold leading-none tracking-[-0.05em] text-foreground tabular-nums">
          <NumberTicker value={days} duration={1200} />
        </span>
        <span className="mt-1 text-[12px] font-semibold uppercase tracking-[0.18em] text-muted-foreground">{t.days}</span>
      </div>
    </div>
  );
}

/* ------------------------------------------------------------------ */
/* seal                                                                */
/* ------------------------------------------------------------------ */

function Seal(p: ViewProps) {
  const { t } = p;
  return (
    <Section className={p.className}>
      <div className="grid grid-cols-[minmax(0,1fr)] items-center gap-12 lg:grid-cols-[minmax(0,22rem)_minmax(0,1fr)] lg:gap-20">
        <div className="kit-rise lg:order-1">
          <DaySeal days={p.days ?? 30} rim={p.rim ?? `${t.moneyBack} · ${t.guarantee}`} t={t} />
        </div>
        <div className="lg:order-2">
          <Head {...p} />
          {p.points?.length ? (
            <ul className="mt-9 grid gap-5 sm:grid-cols-2 lg:grid-cols-1 xl:grid-cols-2">
              {p.points.map((x, i) => (
                <li key={i} className="kit-rise flex gap-3.5" style={delay(140 + i * 70)}>
                  <span className="grid size-9 shrink-0 place-items-center rounded-full bg-[color-mix(in_oklab,var(--success)_14%,var(--card))] text-success-foreground">
                    <Icon name={x.icon ?? 'check'} size={17} strokeWidth={1.9} />
                  </span>
                  <span>
                    <span className="block text-[15px] font-semibold text-foreground">{x.title}</span>
                    {x.text && <span className="mt-0.5 block text-pretty text-[14px] leading-relaxed text-muted-foreground">{x.text}</span>}
                  </span>
                </li>
              ))}
            </ul>
          ) : null}
          <Action cta={p.cta} note={p.note} className="mt-10" />
          {p.fine && <Fine text={p.fine} t={t} className="mt-6" />}
        </div>
      </div>
    </Section>
  );
}

/* ------------------------------------------------------------------ */
/* balance                                                             */
/* ------------------------------------------------------------------ */

function Balance(p: ViewProps) {
  const { t } = p;
  const ref = useScrollProgress<HTMLDivElement>({ start: 0.1, end: 0.55 });
  const r = p.risk ?? { yoursLabel: t.yours, yours: [], oursLabel: t.ours, ours: [] };
  // tilt from level (0) to the side that carries more (our side goes down)
  const tilt = 'calc(var(--kit-progress, 1) * 9deg)';
  return (
    <Section background="surface" className={p.className}>
      <Head {...p} center />
      <div ref={ref} className="relative mx-auto mt-14 max-w-[60rem]">
        {/* the scale */}
        <svg viewBox="0 0 600 150" aria-hidden className="mx-auto block w-full max-w-[40rem] overflow-visible max-sm:hidden">
          <path d="M300 146 300 40" className="stroke-border-strong" strokeWidth="3" strokeLinecap="round" />
          <path d="M262 146h76" className="stroke-border-strong" strokeWidth="3" strokeLinecap="round" />
          <circle cx="300" cy="36" r="7" className="fill-card stroke-border-strong" strokeWidth="2" />
          <g className="motion-reduce:![transform:none]" style={{ transform: `rotate(${tilt})`, transformOrigin: '300px 36px' }}>
            <path d="M90 36H510" className="stroke-foreground" strokeWidth="3" strokeLinecap="round" />
            {/* the pans hang straight, they turn back by the same angle */}
            <g style={{ transform: `rotate(calc(${tilt} * -1))`, transformOrigin: '90px 36px' }} className="motion-reduce:![transform:none]">
              <path d="M90 36 60 96M90 36l30 60" className="stroke-muted-foreground" strokeWidth="1.5" />
              <path d="M50 96h80a40 12 0 0 1-80 0Z" className="fill-card stroke-border-strong" strokeWidth="1.5" />
            </g>
            <g style={{ transform: `rotate(calc(${tilt} * -1))`, transformOrigin: '510px 36px' }} className="motion-reduce:![transform:none]">
              <path d="M510 36 480 96M510 36l30 60" className="stroke-muted-foreground" strokeWidth="1.5" />
              <path d="M470 96h80a40 12 0 0 1-80 0Z" className="fill-[color-mix(in_oklab,var(--primary)_22%,var(--card))] stroke-primary" strokeWidth="1.5" />
              <rect x="492" y="78" width="36" height="18" rx="4" className="fill-primary" />
            </g>
          </g>
        </svg>
        <div className="grid grid-cols-[minmax(0,1fr)] gap-4 sm:mt-2 sm:grid-cols-2 sm:gap-6">
          <div className="kit-rise rounded-2xl border border-border bg-card p-6 shadow-[var(--highlight),var(--shadow-sm)] sm:p-7" style={delay(120)}>
            <p className="flex items-center gap-2 text-[13px] font-medium uppercase tracking-[0.1em] text-muted-foreground">
              <Icon name="user" size={15} />
              {r.yoursLabel}
            </p>
            <ul className="mt-5 grid gap-3">
              {(r.yours.length ? r.yours : [t.nothing]).map((x, i) => (
                <li key={i} className="flex gap-3 text-pretty text-[15px] text-foreground">
                  <Icon name="minus" size={16} className="mt-1 shrink-0 text-subtle-foreground" />
                  {x}
                </li>
              ))}
            </ul>
          </div>
          <div className="kit-rise rounded-2xl border border-[color-mix(in_oklab,var(--primary)_45%,var(--border))] bg-[color-mix(in_oklab,var(--primary)_6%,var(--card))] p-6 shadow-[var(--highlight),0_0_0_4px_var(--glow),var(--shadow-md)] sm:p-7" style={delay(200)}>
            <p className="flex items-center gap-2 text-[13px] font-medium uppercase tracking-[0.1em] text-[color-mix(in_oklab,var(--primary)_75%,var(--foreground))]">
              <Icon name="shield" size={15} />
              {r.oursLabel}
            </p>
            <ul className="mt-5 grid gap-3">
              {r.ours.map((x, i) => (
                <li key={i} className="flex gap-3 text-pretty text-[15px] text-foreground">
                  <Icon name="check" size={16} strokeWidth={2} className="mt-1 shrink-0 text-primary" />
                  {x}
                </li>
              ))}
            </ul>
          </div>
        </div>
      </div>
      <Action cta={p.cta} note={p.note} center className="mt-12" />
    </Section>
  );
}

/* ------------------------------------------------------------------ */
/* refund-steps                                                        */
/* ------------------------------------------------------------------ */

function RefundSteps(p: ViewProps) {
  const steps = p.steps ?? [];
  const ref = useScrollProgress<HTMLDivElement>({ start: 0.15, end: 0.6 });
  return (
    <Section className={p.className}>
      <Head {...p} />
      <div ref={ref} className="relative mt-14" style={{ ['--n' as string]: steps.length }}>
        {/* the track across on wider screens, from the first icon to the last */}
        <span aria-hidden className="absolute left-[calc(50%/var(--n))] right-[calc(50%/var(--n))] top-[21px] h-0.5 rounded-full bg-track max-md:hidden" />
        <span aria-hidden className="absolute left-[calc(50%/var(--n))] right-[calc(50%/var(--n))] top-[21px] h-0.5 origin-left rounded-full bg-primary [transform:scaleX(var(--kit-progress,1))] max-md:hidden" />
        <ol className="relative grid grid-cols-[minmax(0,1fr)] gap-8 md:grid-cols-[repeat(var(--n),minmax(0,1fr))] md:gap-6">
          {steps.map((s, i) => (
            <li key={i} className="relative grid grid-cols-[44px_minmax(0,1fr)] gap-4 md:flex md:flex-col md:items-center md:text-center">
              {/* on phones a segment runs down to the next step, it ends at the last icon */}
              {i < steps.length - 1 && (
                <span aria-hidden className="absolute -bottom-8 left-[21px] top-11 w-0.5 bg-track md:hidden">
                  <span className="block size-full origin-top bg-primary" style={{ transform: `scaleY(clamp(0, calc(var(--kit-progress, 1) * ${Math.max(1, steps.length - 1)} - ${i}), 1))` }} />
                </span>
              )}
              <span className="relative z-[1] grid size-11 place-items-center rounded-full border-2 border-primary bg-background text-primary shadow-[0_0_0_6px_var(--background)]">
                <Icon name={s.icon ?? (i === steps.length - 1 ? 'check' : 'circle')} size={18} strokeWidth={1.9} />
              </span>
              <div className="kit-rise pt-1 md:mt-4 md:pt-0" style={delay(i * 100)}>
                <p className="text-[12.5px] font-semibold uppercase tracking-[0.12em] text-[color-mix(in_oklab,var(--primary)_75%,var(--muted-foreground))]">{s.day}</p>
                <p className="mt-1.5 text-balance text-[17px] font-semibold leading-snug text-foreground">{s.title}</p>
                {s.text && <p className="mt-1.5 text-pretty text-[14.5px] leading-relaxed text-muted-foreground md:mx-auto md:max-w-[16rem]">{s.text}</p>}
              </div>
            </li>
          ))}
        </ol>
      </div>
      <Action cta={p.cta} note={p.note} className="mt-14" />
    </Section>
  );
}

/* ------------------------------------------------------------------ */
/* promises                                                            */
/* ------------------------------------------------------------------ */

function Promises(p: ViewProps) {
  const { t } = p;
  const list = p.promises ?? [];
  const [open, setOpen] = useState<number | null>(null);
  const id = useId();
  return (
    <Section className={p.className}>
      <Head {...p} center />
      <ul className={cn('mt-14 grid grid-cols-[minmax(0,1fr)] gap-px overflow-hidden rounded-2xl border border-border bg-border shadow-[var(--highlight),var(--shadow-sm)]', list.length >= 3 ? 'md:grid-cols-3' : 'md:grid-cols-2', list.length === 4 && 'md:grid-cols-2 lg:grid-cols-4')}>
        {list.map((x, i) => (
          <li key={i} className="flex flex-col bg-card p-6 sm:p-7">
            <div className="kit-rise flex h-full flex-col" style={delay(i * 90)}>
              <Icon name={x.icon ?? 'shield'} size={20} className="text-primary" />
              <p className={cn('mt-6 flex items-baseline font-semibold tracking-[-0.04em] text-foreground', x.unit?.startsWith('%') ? 'gap-0.5' : 'gap-1.5')}>
                <span className="text-[clamp(2.75rem,2.3rem+1.6vw,3.5rem)] leading-none tabular-nums">{typeof x.value === 'number' ? <NumberTicker value={x.value} delay={i * 120} /> : x.value}</span>
                {x.unit && <span className="text-[18px] tracking-[-0.01em] text-muted-foreground">{x.unit}</span>}
              </p>
              <p className="mt-4 text-[16px] font-semibold text-foreground">{x.title}</p>
              {x.text && <p className="mt-1.5 text-pretty text-[14.5px] leading-relaxed text-muted-foreground">{x.text}</p>}
              {x.fine && (
                <div className="mt-auto pt-5">
                  <button
                    type="button"
                    aria-expanded={open === i}
                    aria-controls={`${id}-${i}`}
                    onClick={() => setOpen(open === i ? null : i)}
                    className="inline-flex min-h-11 cursor-pointer items-center gap-1.5 rounded-md text-[13px] font-medium text-muted-foreground hover:text-foreground focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-solid focus-visible:outline-primary sm:min-h-8"
                  >
                    <Icon name="plus" size={14} className={cn('transition-transform duration-300', open === i && 'rotate-45')} />
                    {open === i ? t.hide : t.fine}
                  </button>
                  <div id={`${id}-${i}`} className={cn('grid transition-[grid-template-rows] duration-500 ease-[var(--ease-kit)] motion-reduce:transition-none', open === i ? 'grid-rows-[1fr]' : 'grid-rows-[0fr]')}>
                    <p className={cn('overflow-hidden text-pretty text-[13px] leading-relaxed text-muted-foreground', open !== i && 'invisible')}>
                      <span className="block pt-2">{x.fine}</span>
                    </p>
                  </div>
                </div>
              )}
            </div>
          </li>
        ))}
      </ul>
      <Action cta={p.cta} note={p.note} center className="mt-12" />
    </Section>
  );
}

/* ------------------------------------------------------------------ */
/* inline                                                              */
/* ------------------------------------------------------------------ */

function Inline(p: ViewProps) {
  const { t } = p;
  const [ref, inView] = useInView<HTMLDivElement>();
  const days = p.days ?? 30;
  const C = 2 * Math.PI * 20;
  return (
    <div ref={ref} className={cn('flex flex-col gap-4 rounded-2xl border border-border bg-card p-4 shadow-[var(--highlight),var(--shadow-sm)] sm:flex-row sm:items-center sm:gap-5 sm:p-5', p.className)}>
      <div className="flex items-center gap-4">
        <span className="relative grid size-14 shrink-0 place-items-center">
          <svg viewBox="0 0 48 48" aria-hidden className="absolute inset-0 size-full -rotate-90">
            <circle cx="24" cy="24" r="20" fill="none" className="stroke-track" strokeWidth="3" />
            <circle cx="24" cy="24" r="20" fill="none" className="stroke-success transition-[stroke-dashoffset] duration-[1400ms] ease-[var(--ease-kit)] motion-reduce:transition-none" strokeWidth="3" strokeLinecap="round" strokeDasharray={round(C)} strokeDashoffset={inView ? 0 : round(C)} />
          </svg>
          <span className="relative text-[16px] font-semibold tabular-nums text-foreground">{days}</span>
        </span>
        <span className="min-w-0">
          <span className="block text-[15px] font-semibold text-foreground">{p.title ?? `${days} ${t.days} ${t.moneyBack}`}</span>
          {p.description && <span className="mt-0.5 block text-pretty text-[13.5px] text-muted-foreground">{p.description}</span>}
        </span>
      </div>
      {p.points?.length ? (
        <ul className="grid gap-2 border-t border-border pt-4 text-[13.5px] text-muted-foreground sm:ml-auto sm:border-l sm:border-t-0 sm:pl-5 sm:pt-0">
          {p.points.map((x, i) => (
            <li key={i} className="flex items-center gap-2">
              <Icon name={x.icon ?? 'check'} size={15} className="shrink-0 text-success-foreground" />
              {x.title}
            </li>
          ))}
        </ul>
      ) : null}
    </div>
  );
}

/* ------------------------------------------------------------------ */

const VIEWS: Record<GuaranteeVariant, (p: ViewProps) => React.ReactNode> = {
  seal: Seal,
  balance: Balance,
  'refund-steps': RefundSteps,
  promises: Promises,
  inline: Inline,
};

export function Guarantee({ variant = 'seal', labels, ...rest }: GuaranteeProps) {
  const t = useTexts(TEXTS, labels);
  const View = VIEWS[variant] ?? Seal;
  return <View {...rest} variant={variant} t={t} />;
}

Frequent questions

How do I install Guarantee?

With aiw add @ai-website/guarantee. The command puts components/ui/guarantee.tsx into your project, the code is yours from then on. It brings along motion, button, icon, number-ticker and section. It needs no further npm packages.

Which variants does Guarantee have?

5 variants, Seal with conditions, Risk on our side, How a return works, Promises with small print and Next to the buy button. You pick one with the variant prop, for example variant="balance".

Can I translate the texts of Guarantee?

Yes. Guarantee carries 8 texts of its own in English and German, such as “days”, “Money back” and “The small print”. The language follows the KitProvider, single texts change through the labels prop.

Can an AI assistant build with Guarantee?

Yes. Through the MCP server of the aiw CLI, Claude, Cursor, GitHub Copilot, Windsurf, Codex and Gemini find Guarantee with aiw_components and install it with aiw_add @ai-website/guarantee. Name the variant in your prompt, for example variant="seal", and the assistant writes your content straight into the props.

Often searched

Jump to

Copied to the clipboard