Integrating shadcn/ui with Buttondown

Build a newsletter subscription form with shadcn/ui components.

shadcn/ui is a collection of re-usable components that you can copy and paste into your apps. You can install different shadcn/ui components and use them to create a subscription component for your newsletter.

Getting started

To use the examples below, you'll need to install the Card, Button, Input, and Label components:

npx shadcn-ui@latest add button card input label

You may notice that we do not install the Form component. That's intentional; shadcn's form boilerplate pulls in React Hook Form and Zod, both of which are great libraries but overkill for simple subscription forms. If you've already installed them and want to use them, you can, but use the URL shown in the examples below for the action prop of Form.

In the examples, replace {username} with your Buttondown username.

Basic example

<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>shadcn/ui Buttondown Example - Basic</title>
    <script src="https://cdn.tailwindcss.com"></script>
    <script>
      tailwind.config = {
        theme: {
          extend: {
            colors: {
              border: "hsl(214.3 31.8% 91.4%)",
              input: "hsl(214.3 31.8% 91.4%)",
              ring: "hsl(222.2 84% 4.9%)",
              background: "hsl(0 0% 100%)",
              foreground: "hsl(222.2 84% 4.9%)",
              primary: {
                DEFAULT: "hsl(222.2 47.4% 11.2%)",
                foreground: "hsl(210 40% 98%)",
              },
              secondary: {
                DEFAULT: "hsl(210 40% 96.1%)",
                foreground: "hsl(222.2 47.4% 11.2%)",
              },
              muted: {
                DEFAULT: "hsl(210 40% 96.1%)",
                foreground: "hsl(215.4 16.3% 46.9%)",
              },
              card: {
                DEFAULT: "hsl(0 0% 100%)",
                foreground: "hsl(222.2 84% 4.9%)",
              },
            },
            borderRadius: {
              lg: "0.5rem",
              md: "calc(0.5rem - 2px)",
              sm: "calc(0.5rem - 4px)",
            },
          },
        },
      }
    </script>
    <style>
      * { box-sizing: border-box; }
      body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", sans-serif; }
    </style>
  </head>
  <body class="min-h-screen flex items-center justify-center bg-gray-50">
    <div class="w-full max-w-sm mx-auto rounded-lg border bg-card text-card-foreground shadow-sm">
      <form
        action="https://buttondown.com/api/emails/embed-subscribe/{username}"
        method="post"
      >
        <div class="flex flex-col space-y-1.5 p-6">
          <h3 class="text-2xl font-semibold leading-none tracking-tight">Stay informed</h3>
          <p class="text-sm text-muted-foreground">
            You'll be the first to know when we launch.
          </p>
        </div>
        <div class="p-6 pt-0 grid gap-4">
          <div class="grid gap-2">
            <label class="text-sm font-medium leading-none" for="email">Email</label>
            <input
              class="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
              id="email"
              name="email"
              type="email"
              placeholder="[email protected]"
              required
            />
          </div>
        </div>
        <div class="flex items-center p-6 pt-0">
          <button class="inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 bg-primary text-primary-foreground hover:bg-primary/90 h-10 px-4 py-2 w-full" type="submit">
            Subscribe
          </button>
        </div>
      </form>
    </div>
  </body>
</html>
Preview

With dropdown

<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>shadcn/ui Buttondown Example - Dropdown</title>
    <script src="https://cdn.tailwindcss.com"></script>
    <script src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
    <script src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
    <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
    <script>
      tailwind.config = {
        theme: {
          extend: {
            colors: {
              border: "hsl(214.3 31.8% 91.4%)",
              input: "hsl(214.3 31.8% 91.4%)",
              ring: "hsl(222.2 84% 4.9%)",
              background: "hsl(0 0% 100%)",
              foreground: "hsl(222.2 84% 4.9%)",
              primary: {
                DEFAULT: "hsl(222.2 47.4% 11.2%)",
                foreground: "hsl(210 40% 98%)",
              },
              secondary: {
                DEFAULT: "hsl(210 40% 96.1%)",
                foreground: "hsl(222.2 47.4% 11.2%)",
              },
              muted: {
                DEFAULT: "hsl(210 40% 96.1%)",
                foreground: "hsl(215.4 16.3% 46.9%)",
              },
              card: {
                DEFAULT: "hsl(0 0% 100%)",
                foreground: "hsl(222.2 84% 4.9%)",
              },
            },
            borderRadius: {
              lg: "0.5rem",
              md: "calc(0.5rem - 2px)",
              sm: "calc(0.5rem - 4px)",
            },
          },
        },
      }
    </script>
    <style>
      * { box-sizing: border-box; }
      body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", sans-serif; }
    </style>
  </head>
  <body class="min-h-screen flex items-center justify-center bg-gray-50">
    <div id="root"></div>

    <script type="text/babel">
      function SubscribeForm() {
        const [language, setLanguage] = React.useState("english");

        return (
          <div className="w-full max-w-sm mx-auto rounded-lg border bg-card text-card-foreground shadow-md">
            <form
              action="https://buttondown.com/api/emails/embed-subscribe/{username}"
              method="post"
            >
              <div className="flex flex-col space-y-1.5 p-6">
                <h3 className="text-2xl font-semibold leading-none tracking-tight">Stay informed</h3>
                <p className="text-sm text-muted-foreground">
                  You'll be the first to know when we launch.
                </p>
              </div>
              <div className="p-6 pt-0 grid gap-4">
                <div className="grid gap-2">
                  <label className="text-sm font-medium leading-none" htmlFor="email">Email</label>
                  <input
                    className="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
                    id="email"
                    name="email"
                    type="email"
                    placeholder="[email protected]"
                    required
                  />
                </div>

                <div className="grid gap-2">
                  <input type="hidden" name="tag" value={language} />
                  <label className="text-sm font-medium leading-none" htmlFor="language">Language</label>
                  <select
                    className="flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2"
                    value={language}
                    onChange={(e) => setLanguage(e.target.value)}
                  >
                    <option value="english">English</option>
                    <option value="french">French</option>
                    <option value="spanish">Spanish</option>
                  </select>
                </div>
              </div>
              <div className="flex items-center p-6 pt-0">
                <button className="inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 bg-primary text-primary-foreground hover:bg-primary/90 h-10 px-4 py-2 w-full" type="submit">
                  {language === "english"
                    ? "Subscribe"
                    : language === "french"
                      ? "S'abonner"
                      : "Suscribirse"}
                </button>
              </div>
            </form>
          </div>
        );
      }

      const root = ReactDOM.createRoot(document.getElementById('root'));
      root.render(<SubscribeForm />);
    </script>
  </body>
</html>
Preview
Buttondown is the last email platform you’ll switch to.