Skip to content
All Taste components

Features

Features — tabs switching panels

Four large tabs across the top; picking one swaps the panel underneath with its words and picture.

4 tabs with icon, title and hint · underline on the active tab · text + picture panel · 2 × 2 tabs on small screens

How Fernbill fits your week

One place for every step of getting paid

Quotes that turn into work

Send a clear quote with options. When the client accepts, it becomes an invoice — no retyping.

  • Optional extras clients can tick
  • Accept and sign on any phone

Invoices with your name on them

Your logo, colours and terms on every invoice. Numbers go up on their own; taxes add themselves.

  • Your branding, set once
  • Tax and discounts per line

Reminders that sound like you

Pick the days and the tone. Fernbill sends the follow-ups and stops the second the money lands.

  • Friendly, firm or final tone
  • Skips weekends and holidays

Reports in plain numbers

See what came in, what's owed and what you'll owe in tax — by month, client or currency.

  • Export CSV or PDF
  • Share read-only with your accountant
"use client";
import { useState } from "react";
import { BarChart3, Check, FileText, Mail, Receipt, type LucideIcon } from "lucide-react";
import { cn } from "@/lib/utils";

type Feature = {
  id: string; icon: LucideIcon; tab: string; hint: string;
  title: string; text: string; points: string[];
  card: { title: string; tag: string; lines: [string, string][] };
};
const features: Feature[] = [
  { id: "quote", icon: FileText, tab: "Quote", hint: "Turn a quote into an invoice in one click.",
    title: "Quotes that turn into work", text: "Send a clear quote with options. When the client accepts, it becomes an invoice — no retyping.",
    points: ["Optional extras clients can tick", "Accept and sign on any phone"],
    card: { title: "Quote Q-208", tag: "Draft", lines: [["Website redesign", "$4,800"], ["Extra page (optional)", "$350"], ["Hosting, 12 months", "$180"]] } },
  { id: "invoice", icon: Receipt, tab: "Invoice", hint: "Branded, numbered and sent in a minute.",
    title: "Invoices with your name on them", text: "Your logo, colours and terms on every invoice. Numbers go up on their own; taxes add themselves.",
    points: ["Your branding, set once", "Tax and discounts per line"],
    card: { title: "Invoice INV-1043", tag: "Sent", lines: [["Design sprint", "$2,400"], ["Copywriting, 6 pages", "$900"], ["Tax (18%)", "$594"]] } },
  { id: "remind", icon: Mail, tab: "Remind", hint: "Nudges that stop the moment you’re paid.",
    title: "Reminders that sound like you", text: "Pick the days and the tone. Fernbill sends the follow-ups and stops the second the money lands.",
    points: ["Friendly, firm or final tone", "Skips weekends and holidays"],
    card: { title: "Reminder plan", tag: "On", lines: [["3 days before due", "Friendly"], ["On the due date", "Friendly"], ["7 days late", "Firm"]] } },
  { id: "report", icon: BarChart3, tab: "Report", hint: "Totals your accountant will thank you for.",
    title: "Reports in plain numbers", text: "See what came in, what’s owed and what you’ll owe in tax — by month, client or currency.",
    points: ["Export CSV or PDF", "Share read-only with your accountant"],
    card: { title: "Q3 summary", tag: "Jul – Sep", lines: [["Paid this quarter", "$48,210"], ["Outstanding", "$8,420"], ["Tax set aside", "$7,230"]] } },
];

export function FeaturesTabs() {
  const [active, setActive] = useState(features[0].id);
  const f = features.find((x) => x.id === active) ?? features[0];
  return (
    <section aria-labelledby="features-title" className="bg-bg font-sans">
      <div className="mx-auto max-w-6xl px-4 py-16 sm:px-6 lg:py-24">
        <p className="text-[13px] font-medium text-primary">How Fernbill fits your week</p>
        <h2 id="features-title" className="mt-3.5 max-w-2xl text-balance text-3xl font-semibold tracking-tight text-fg sm:text-4xl">
          One place for every step of getting paid
        </h2>

        <div role="tablist" aria-label="Features" className="mb-9 mt-12 grid grid-cols-2 gap-y-2 border-b border-border md:grid-cols-4">
          {features.map(({ id, icon: Icon, tab, hint }) => (
            <button
              key={id}
              type="button"
              role="tab"
              id={"tab-" + id}
              aria-selected={active === id}
              aria-controls={"panel-" + id}
              onClick={() => setActive(id)}
              className="group -mb-px grid justify-items-start gap-1.5 border-b-2 border-transparent pb-4 pr-4 text-left text-fg-muted transition hover:text-fg aria-selected:border-primary aria-selected:text-fg"
            >
              <Icon className="size-5 group-aria-selected:text-primary" aria-hidden />
              <span className="text-[15px] font-semibold tracking-tight">{tab}</span>
              <span className="hidden text-[13px] leading-snug text-fg-subtle md:block">{hint}</span>
            </button>
          ))}
        </div>

        <div role="tabpanel" id={"panel-" + f.id} aria-labelledby={"tab-" + f.id} className="grid items-center gap-6 lg:grid-cols-[1fr_1.3fr] lg:gap-12">
          <div className="flex flex-col items-start gap-4">
            <h3 className="text-balance text-2xl font-semibold tracking-tight text-fg">{f.title}</h3>
            <p className="text-pretty text-[14.5px] leading-relaxed text-fg-muted">{f.text}</p>
            <ul className="grid gap-2.5 text-sm text-fg">
              {f.points.map((p) => (
                <li key={p} className="flex gap-2.5"><Check className="mt-0.5 size-4 shrink-0 text-primary" aria-hidden />{p}</li>
              ))}
            </ul>
          </div>
          <div aria-hidden className="grid min-h-[260px] place-items-center rounded-xl bg-surface bg-gradient-to-br from-primary-soft to-surface p-5 sm:p-8">
            <div className="grid w-full max-w-[340px] gap-2.5 rounded-lg border border-border bg-bg p-4 text-[12.5px] text-fg shadow-xl">
              <div className="flex items-center justify-between">
                <span className="font-semibold">{f.card.title}</span>
                <span className="rounded-full bg-surface-2 px-2 py-0.5 text-xs text-fg-muted">{f.card.tag}</span>
              </div>
              <hr className="border-border" />
              {f.card.lines.map(([a, b]) => (
                <div key={a} className={cn("flex justify-between gap-3")}><span className="truncate">{a}</span><span className="tabular-nums">{b}</span></div>
              ))}
            </div>
          </div>
        </div>
      </div>
    </section>
  );
}

The code uses the Taste Tailwind preset. Make yours on the Taste page with Export my system.