Timace

Call to action

FreeNo card needed to begin

Start on the page you had in mind

Set up in a few minutes, and yours to take with you.

Joined by 2,000 teams
components/ui/cta.tsx
import type { ReactNode } from "react";

const action = "inline-flex h-12 items-center justify-center gap-2 rounded-[calc(var(--tui-cta-radius)_*_0.4)] px-6 text-[15px] font-semibold no-underline transition-[filter,background-color,scale] duration-(--tui-duration) active:scale-[0.98]";
// The first button is cut from the band's text colour and lifted off it; the second is a pane of glass on it.
const lifted =
  "bg-(--tui-cta-band-fg) text-(--tui-cta-band-bg) [background-image:linear-gradient(to_bottom,rgb(255_255_255_/_0.5),rgb(255_255_255_/_0)_50%,rgb(0_0_0_/_0.08))] [box-shadow:inset_0_1px_0_rgb(255_255_255_/_0.6),inset_0_-1px_0_rgb(0_0_0_/_0.14),0_1px_2px_rgb(0_0_0_/_0.25),0_14px_28px_-12px_rgb(0_0_0_/_0.55)] hover:brightness-95";
const glass =
  "bg-[color-mix(in_srgb,var(--tui-cta-band-fg)_10%,transparent)] text-(--tui-cta-band-fg) backdrop-blur-sm [box-shadow:inset_0_0_0_1px_color-mix(in_srgb,var(--tui-cta-band-fg)_22%,transparent),inset_0_1px_0_color-mix(in_srgb,var(--tui-cta-band-fg)_18%,transparent)]";

export interface CtaProps {
  /** A short line in a capsule over the headline, and a word to mark it with. */
  eyebrow?: ReactNode;
  tag?: string;
  title: ReactNode;
  description?: ReactNode;
  primary?: { label: string; href: string };
  secondary?: { label: string; href: string };
  /** A line of proof under the buttons: initials of a few people, and what to say beside them. */
  proof?: { people: string[]; text: ReactNode };
  className?: string;
}

/**
 * The ask, at the end of a page. A filled band set in a shell, with rings
 * rising from its lower edge and light pooling in two corners, so a flat
 * colour reads as a lit surface. The first button is lifted off it and the
 * second is glass on it.
 */
export function Cta({ eyebrow, tag, title, description, primary, secondary, proof, className }: CtaProps) {
  return (
    <section data-slot="cta" className={["flex w-full justify-center bg-(--tui-cta-bg) px-6 py-(--tui-cta-padding-y) font-(family-name:--tui-font)", className].filter(Boolean).join(" ")}>
      <div data-slot="cta-shell" className="w-full max-w-(--tui-cta-width) rounded-(--tui-cta-radius) bg-(--tui-cta-shell-bg) p-1.5 [box-shadow:inset_0_0_0_1px_var(--tui-cta-border)]">
        <div
          data-slot="cta-band"
          className="relative isolate flex flex-col items-center gap-6 overflow-hidden rounded-[calc(var(--tui-cta-radius)_-_6px)] bg-(--tui-cta-band-bg) px-8 py-(--tui-cta-band-padding) text-center text-(--tui-cta-band-fg) [background-image:linear-gradient(to_bottom,rgb(255_255_255_/_0.1),rgb(255_255_255_/_0)_40%,rgb(0_0_0_/_0.22))] [box-shadow:inset_0_1px_0_rgb(255_255_255_/_0.22),inset_0_0_0_1px_rgb(255_255_255_/_0.06),0_1px_2px_rgb(0_0_0_/_0.2),0_30px_60px_-30px_var(--tui-cta-band-bg)]"
        >
          <span
            aria-hidden
            data-slot="cta-rings"
            className="pointer-events-none absolute bottom-0 left-1/2 -z-10 size-[72rem] -translate-x-1/2 translate-y-[58%] rounded-full opacity-(--tui-cta-pattern-strength) [background-image:repeating-radial-gradient(circle_at_center,transparent_0_63px,currentColor_63px_64px)] [mask-image:linear-gradient(to_bottom,black,transparent_46%)]"
          />
          <span aria-hidden className="pointer-events-none absolute -top-40 -left-24 -z-10 size-[26rem] rounded-full bg-(--tui-cta-light) blur-3xl" />
          <span aria-hidden className="pointer-events-none absolute -right-28 -bottom-44 -z-10 size-[30rem] rounded-full bg-(--tui-cta-light) blur-3xl" />

          {eyebrow && (
            <span data-slot="cta-eyebrow" className={`inline-flex h-8 items-center gap-2.5 rounded-full pr-3.5 pl-1 text-[13px] font-medium ${glass}`}>
              {tag && <span className="inline-flex h-6 items-center rounded-full bg-(--tui-cta-band-fg) px-2.5 text-[11px] font-semibold tracking-[0.04em] text-(--tui-cta-band-bg) uppercase [box-shadow:0_1px_2px_rgb(0_0_0_/_0.25)]">{tag}</span>}
              {eyebrow}
            </span>
          )}
          <h2 className="m-0 max-w-[42rem] text-(length:--tui-cta-title-size) leading-[1.04] font-semibold tracking-[-0.035em] text-balance">{title}</h2>
          {description && <p className="m-0 max-w-[32rem] text-[18px] leading-relaxed text-pretty opacity-75">{description}</p>}
          {(primary || secondary) && (
            <div className="mt-2 flex flex-wrap items-center justify-center gap-3">
              {primary && (
                <a href={primary.href} data-slot="cta-primary" className={`${action} ${lifted}`}>
                  {primary.label}
                  <svg aria-hidden viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round" className="size-4">
                    <path d="M3.500 8h9M8.500 4l4 4-4 4" />
                  </svg>
                </a>
              )}
              {secondary && (
                <a href={secondary.href} data-slot="cta-secondary" className={`${action} ${glass} hover:bg-[color-mix(in_srgb,var(--tui-cta-band-fg)_16%,transparent)]`}>
                  {secondary.label}
                </a>
              )}
            </div>
          )}
          {proof && (
            <div data-slot="cta-proof" className="mt-2 flex items-center gap-3 text-[14px]">
              <span aria-hidden className="flex">
                {proof.people.slice(0, 5).map((initials, i) => (
                  <span key={i} className="-ml-2 flex size-8 items-center justify-center rounded-full bg-[color-mix(in_srgb,var(--tui-cta-band-fg)_20%,var(--tui-cta-band-bg))] text-[11px] font-semibold first:ml-0 [box-shadow:0_0_0_2px_var(--tui-cta-band-bg)]">
                    {initials}
                  </span>
                ))}
              </span>
              <span className="opacity-75">{proof.text}</span>
            </div>
          )}
        </div>
      </div>
    </section>
  );
}
styles/timace-ui.css
:root {
  color-scheme: light;
  --tui-bg: #ffffff;
  --tui-fg: #0a0a0a;
  --tui-muted: #f5f5f5;
  --tui-muted-fg: #737373;
  --tui-border: #e5e5e5;
  --tui-primary: #171717;
  --tui-primary-fg: #fafafa;
  --tui-ring: #a3a3a3;
  --tui-success: #16a34a;
  --tui-warning: #d97706;
  --tui-danger: #dc2626;
  --tui-radius: 8px;
  --tui-border-width: 1px;
  --tui-space: 4px;
  --tui-shadow: none;
  --tui-font: ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
  --tui-text: 14px;
  --tui-duration: 150ms;

  /* Call to action */
  --tui-cta-padding-y: calc(var(--tui-space) * 12);
  --tui-cta-band-padding: calc(var(--tui-space) * 24);
  --tui-cta-width: 1040px;
  --tui-cta-radius: calc(var(--tui-radius) * 4);
  --tui-cta-title-size: 52px;
  --tui-cta-pattern-strength: 0.16;
  --tui-cta-bg: var(--tui-bg);
  --tui-cta-shell-bg: var(--tui-muted);
  --tui-cta-border: var(--tui-border);
  --tui-cta-band-bg: var(--tui-primary);
  --tui-cta-band-fg: var(--tui-primary-fg);
  --tui-cta-light: color-mix(in srgb, var(--tui-primary-fg) 22%, transparent);
}

.dark,
[data-theme="dark"] {
  color-scheme: dark;
  --tui-bg: #0a0a0a;
  --tui-fg: #fafafa;
  --tui-muted: #1c1c1c;
  --tui-muted-fg: #a3a3a3;
  --tui-border: #2a2a2a;
  --tui-primary: #fafafa;
  --tui-primary-fg: #171717;
  --tui-ring: #737373;
  --tui-success: #22c55e;
  --tui-warning: #f59e0b;
  --tui-danger: #ef4444;
}

Needs Tailwind CSS v4. Import the stylesheet once, after Tailwind.