New Repo Setup

How this project was created with Vite, plus Tailwind v4 and ESLint setup steps you can reuse.

Create the project

Start a fresh React + TypeScript app with Vite:

npm create vite@latest my-app 
cd my-app
npm install

Add Tailwind CSS (v4)

  1. Install Tailwind and the Vite plugin:
    npm install -D tailwindcss @tailwindcss/vite
  2. Update vite.config.ts to include the plugin:
    import { defineConfig } from "vite";
    import react from "@vitejs/plugin-react";
    import tailwindcss from "@tailwindcss/vite";
    
    export default defineConfig({
      plugins: [tailwindcss(), react()],
    });
  3. Create your global CSS (e.g., src/index.css or app/app.css) with Tailwind imports and any base theme:
    @import "tailwindcss";
    
    @theme {
      --font-sans: "Inter", ui-sans-serif, system-ui, sans-serif;
    }
    
    html, body {
      @apply bg-white text-slate-900 dark:bg-slate-950 dark:text-slate-100;
    }
  4. Import that CSS in your root entry (e.g., main.tsx or app/root.tsx).

Add ESLint (flat config, ESLint 9)

  1. Install lint deps:
    npm install -D eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin eslint-plugin-react eslint-plugin-react-hooks eslint-plugin-jsx-a11y globals
  2. Add eslint.config.js:
    import js from "@eslint/js";
    import globals from "globals";
    import tsParser from "@typescript-eslint/parser";
    import tsPlugin from "@typescript-eslint/eslint-plugin";
    import reactPlugin from "eslint-plugin-react";
    import reactHooks from "eslint-plugin-react-hooks";
    import jsxA11y from "eslint-plugin-jsx-a11y";
    
    export default [
      { ignores: ["**/node_modules/**", "**/build/**", "**/dist/**"] },
      {
        files: ["**/*.{ts,tsx}"],
        languageOptions: {
          parser: tsParser,
          parserOptions: { ecmaVersion: "latest", sourceType: "module", ecmaFeatures: { jsx: true } },
          globals: { ...globals.browser, ...globals.node },
        },
        settings: { react: { version: "detect" } },
        plugins: {
          "@typescript-eslint": tsPlugin,
          react: reactPlugin,
          "react-hooks": reactHooks,
          "jsx-a11y": jsxA11y,
        },
        rules: {
          ...js.configs.recommended.rules,
          ...tsPlugin.configs.recommended.rules,
          ...reactPlugin.configs.recommended.rules,
          ...reactHooks.configs.recommended.rules,
          ...jsxA11y.configs.recommended.rules,
          "react/react-in-jsx-scope": "off",
          "react/prop-types": "off",
          "no-undef": "off",
          "@typescript-eslint/no-unused-vars": ["warn", { argsIgnorePattern: "^_", varsIgnorePattern: "^_" }],
        },
      },
    ];
  3. Add a lint script to package.json:
    "scripts": {
      "lint": "eslint .",
      "dev": "vite",
      "build": "vite build",
      "preview": "vite preview",
      "typecheck": "tsc --noEmit"
    }
  4. Run npm run lint to verify.

Regular Buttons

Buttons

Use the base Button component with the color prop to choose any Tailwind default color. Variants like Ghost, Outline, and Windows95 are provided for common patterns.

import { Button, GhostButton, OutlineButton } from "./buttons/regular-buttons";

<Button color="emerald">Save</Button>
<Button color="rose" disabled>Disabled</Button>
<GhostButton onClick={...}>Ghost action</GhostButton>
<OutlineButton type="submit">Submit</OutlineButton>

Copy the regular-buttons file content

This will copy the tsx file with all of the buttons and their content to your clipboard.

Inputs

Reusable form inputs with labels, helper text, and error states. All variants are Tailwind-only, with a nostalgic Windows 95 input for fun.

import { TextInput, PasswordInput, FileInput } from "./inputs/inputs";

<TextInput label="Name" placeholder="Jane Doe" />
<PasswordInput label="Password" helperText="At least 8 characters" />
<FileInput label="Resume" accept=".pdf" multiple />

Copy the inputs file

Upload filePDF, PNG, JPG up to 10MB

Text Areas

A flexible textarea with label, helper text, error states, resize controls, and an optional character counter—styled to match the existing inputs.

import { TextArea } from "./text-area/textarea";

<TextArea label="Message" placeholder="Type away..." />
<TextArea label="Notes" helperText="Max 200 characters" maxLength={200} showCount />
<TextArea label="Error state" error="Please fill this in" />

Copy the textarea file