Skip to content
Notra logoNotra UI
Esc
↑↓navigate↵open⌘Jpreview
On this page

Marketing Button

The gradient marketing buttons from the Notra landing page.

import { ArrowRight } from "lucide-react";

import { MarketingButton } from "../components/marketing-button";

export default function MarketingButtonDemo() {
  return (
    <div className="flex w-full min-w-0 flex-wrap items-center justify-center gap-6 p-8">
      <MarketingButton>
        Start for free
        <ArrowRight />
      </MarketingButton>
      <MarketingButton variant="light">Book a call</MarketingButton>
    </div>
  );
}
$ bunx --bun shadcn@latest add @notra/marketing-button

MarketingButton is built on Base UI’s Button, like the shadcn Button, so it takes every Button prop, including a className callback. The gradients and hover states ship as three Tailwind utilities, marketing-gradient-primary, marketing-gradient-flat and marketing-gradient-light, added to your CSS by the install.

Prop Default Description
variant primary primary is the purple gradient, flat is the same gradient without the halo, light is the white one.
size default sm is 40px tall, default is 44px, lg is 48px.
loading false Swaps the label for a spinner, like Button’s loading state, and keeps the button’s width. The button stays focusable and ignores clicks.
render none Base UI’s render prop. Pass an element to render a link.
nativeButton true Set to false when render is not a <button>.

Primary

The purple gradient, for the main call to action.

import { ArrowRight } from "lucide-react";

import { MarketingButton } from "../components/marketing-button";

export default function MarketingButtonPrimary() {
  return (
    <div className="flex w-full min-w-0 flex-wrap items-center justify-center gap-6 p-8">
      <MarketingButton>
        Start for free
        <ArrowRight />
      </MarketingButton>
    </div>
  );
}

Secondary

The white gradient with variant="light", for the action next to it.

import { MarketingButton } from "../components/marketing-button";

export default function MarketingButtonSecondary() {
  return (
    <div className="flex w-full min-w-0 flex-wrap items-center justify-center gap-6 p-8">
      <MarketingButton variant="light">Book a call</MarketingButton>
    </div>
  );
}

Loading

Set loading while the action runs. The label blurs up and out and a spinner rises in at its center, the same swap as Button with a spinner instead of dots. The button keeps its width, so nothing around it moves.

"use client";

import { ArrowRight } from "lucide-react";
import { useState } from "react";

import {
  MarketingButton,
  type MarketingButtonProps,
} from "../components/marketing-button";

const LOADING_DURATION_MS = 2400;

function LoadingMarketingButton(
  props: Omit<MarketingButtonProps, "loading" | "onClick">
) {
  const [loading, setLoading] = useState(false);

  const handleClick = () => {
    setLoading(true);
    setTimeout(() => setLoading(false), LOADING_DURATION_MS);
  };

  return <MarketingButton {...props} loading={loading} onClick={handleClick} />;
}

export default function MarketingButtonLoading() {
  return (
    <div className="flex w-full min-w-0 flex-wrap items-center justify-center gap-6 p-8">
      <LoadingMarketingButton>
        Start for free
        <ArrowRight />
      </LoadingMarketingButton>
      <LoadingMarketingButton variant="light">
        Book a call
      </LoadingMarketingButton>
    </div>
  );
}

Render an anchor or your router’s link and set nativeButton={false}.

import { MarketingButton } from "../components/marketing-button";

export default function MarketingButtonLink() {
  return (
    <div className="flex w-full min-w-0 flex-wrap items-center justify-center gap-6 p-8">
      <MarketingButton
        nativeButton={false}
        render={<a href="https://usenotra.com" rel="noopener" />}
      >
        Visit Notra
      </MarketingButton>
    </div>
  );
}