├── .gitignore ├── LICENSE ├── README.md ├── app ├── apple-touch-icon.png ├── editor │ └── theme │ │ └── page.tsx ├── favicon-16x16.png ├── favicon-32x32.png ├── favicon.ico ├── globals.css ├── layout.tsx ├── not-found.tsx └── page.tsx ├── assets ├── buymeacoffee.svg ├── discord.svg ├── github.svg ├── heart.svg ├── logo.svg ├── og-image.png └── twitter.svg ├── components ├── editor │ ├── action-bar.tsx │ ├── code-panel-dialog.tsx │ ├── code-panel.tsx │ ├── color-picker.tsx │ ├── contrast-checker.tsx │ ├── control-section.tsx │ ├── css-import-dialog.tsx │ ├── editor.tsx │ ├── header.tsx │ ├── shadow-control.tsx │ ├── slider-with-input.tsx │ ├── theme-control-actions.tsx │ ├── theme-control-panel.tsx │ ├── theme-font-select.tsx │ ├── theme-preset-select.tsx │ ├── theme-preview-panel.tsx │ └── theme-preview │ │ ├── color-preview.tsx │ │ ├── components-showcase.tsx │ │ ├── examples-preview-container.tsx │ │ └── tabs-trigger-pill.tsx ├── examples │ ├── cards │ │ ├── chat.tsx │ │ ├── cookie-settings.tsx │ │ ├── create-account.tsx │ │ ├── date-picker-with-range.tsx │ │ ├── date-picker.tsx │ │ ├── font-showcase.tsx │ │ ├── github-card.tsx │ │ ├── notifications.tsx │ │ ├── payment-method.tsx │ │ ├── report-an-issue.tsx │ │ ├── share-document.tsx │ │ ├── stats.tsx │ │ └── team-members.tsx │ ├── dashboard │ │ ├── components │ │ │ ├── app-sidebar.tsx │ │ │ ├── chart-area-interactive.tsx │ │ │ ├── data-table.tsx │ │ │ ├── nav-documents.tsx │ │ │ ├── nav-main.tsx │ │ │ ├── nav-secondary.tsx │ │ │ ├── nav-user.tsx │ │ │ ├── section-cards.tsx │ │ │ └── site-header.tsx │ │ ├── data.json │ │ └── index.tsx │ ├── demo-cards.tsx │ ├── mail │ │ ├── components │ │ │ ├── account-switcher.tsx │ │ │ ├── mail-display.tsx │ │ │ ├── mail-list.tsx │ │ │ ├── mail.tsx │ │ │ └── nav.tsx │ │ ├── data.tsx │ │ ├── index.tsx │ │ └── use-mail.ts │ ├── music │ │ ├── components │ │ │ ├── album-artwork.tsx │ │ │ ├── menu.tsx │ │ │ ├── podcast-empty-placeholder.tsx │ │ │ └── sidebar.tsx │ │ ├── data │ │ │ ├── albums.ts │ │ │ └── playlists.ts │ │ └── index.tsx │ └── tasks │ │ ├── components │ │ ├── columns.tsx │ │ ├── data-table-column-header.tsx │ │ ├── data-table-faceted-filter.tsx │ │ ├── data-table-pagination.tsx │ │ ├── data-table-row-actions.tsx │ │ ├── data-table-toolbar.tsx │ │ ├── data-table-view-options.tsx │ │ ├── data-table.tsx │ │ └── user-nav.tsx │ │ ├── data │ │ ├── data.tsx │ │ ├── schema.ts │ │ └── tasks.json │ │ └── index.tsx ├── home │ ├── cta.tsx │ ├── faq.tsx │ ├── features.tsx │ ├── footer.tsx │ ├── header.tsx │ ├── hero.tsx │ ├── how-it-works.tsx │ ├── roadmap.tsx │ └── theme-preset-selector.tsx ├── icons.tsx ├── loading.tsx ├── posthog-init.tsx ├── social-link.tsx ├── theme-provider.tsx ├── theme-script.tsx └── ui │ ├── accordion.tsx │ ├── alert-dialog.tsx │ ├── alert.tsx │ ├── aspect-ratio.tsx │ ├── avatar.tsx │ ├── badge.tsx │ ├── breadcrumb.tsx │ ├── button.tsx │ ├── calendar.tsx │ ├── card.tsx │ ├── carousel.tsx │ ├── chart.tsx │ ├── checkbox.tsx │ ├── collapsible.tsx │ ├── command.tsx │ ├── context-menu.tsx │ ├── dialog.tsx │ ├── drawer.tsx │ ├── dropdown-menu.tsx │ ├── form.tsx │ ├── hover-card.tsx │ ├── input-otp.tsx │ ├── input.tsx │ ├── label.tsx │ ├── menubar.tsx │ ├── navigation-menu.tsx │ ├── pagination.tsx │ ├── popover.tsx │ ├── progress.tsx │ ├── radio-group.tsx │ ├── resizable.tsx │ ├── scroll-area.tsx │ ├── select.tsx │ ├── separator.tsx │ ├── sheet.tsx │ ├── sidebar.tsx │ ├── skeleton.tsx │ ├── slider.tsx │ ├── sonner.tsx │ ├── switch.tsx │ ├── table.tsx │ ├── tabs.tsx │ ├── textarea.tsx │ ├── toast.tsx │ ├── toaster.tsx │ ├── toggle-group.tsx │ ├── toggle.tsx │ ├── tooltip.tsx │ └── use-toast.ts ├── config ├── editors │ ├── index.ts │ └── theme.ts └── theme.ts ├── eslint.config.mjs ├── hooks ├── use-contrast-checker.ts ├── use-fullscreen.ts ├── use-github-stars.ts ├── use-mobile.tsx ├── use-theme-preset-from-url.ts └── use-toast.ts ├── lib ├── posthog.ts └── utils.ts ├── next.config.ts ├── package.json ├── pnpm-lock.yaml ├── postcss.config.mjs ├── public ├── file.svg ├── globe.svg ├── next.svg ├── og-image.png ├── vercel.svg └── window.svg ├── scripts └── generate-theme-registry.ts ├── store ├── editor-store.ts └── preferences-store.ts ├── stubs └── use-effect-event.js ├── tsconfig.json ├── types ├── editor.ts ├── index.ts └── theme.ts └── utils ├── apply-style-to-element.ts ├── color-converter.ts ├── contrast-checker.ts ├── debounce.ts ├── parse-css-input.ts ├── shadows.ts ├── theme-fonts.ts ├── theme-presets.ts └── theme-style-generator.ts /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | node_modules 5 | /.pnp 6 | .pnp.* 7 | .yarn/* 8 | !.yarn/patches 9 | !.yarn/plugins 10 | !.yarn/releases 11 | !.yarn/versions 12 | 13 | # bun lock 14 | bun.lock 15 | 16 | # testing 17 | /coverage 18 | 19 | # next.js 20 | /.next/ 21 | /out/ 22 | 23 | # production 24 | /build 25 | 26 | # misc 27 | .DS_Store 28 | *.pem 29 | 30 | # debug 31 | npm-debug.log* 32 | yarn-debug.log* 33 | yarn-error.log* 34 | .pnpm-debug.log* 35 | 36 | # env files (can opt-in for committing if needed) 37 | .env* 38 | 39 | # vercel 40 | .vercel 41 | 42 | # typescript 43 | *.tsbuildinfo 44 | next-env.d.ts 45 | 46 | # build artifacts 47 | public/r/themes 48 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 |

tweakcn.com

3 |
4 | 5 |
6 | 7 | Discord 8 | 9 | GitHub Repo stars 10 | 11 | X (formerly Twitter) URL 12 | 13 |
14 | 15 |
16 | 17 | **[tweakcn](https://tweakcn.com)** is a powerful Visual Theme Editor for tailwind CSS & shadcn/ui components. It comes with Beautiful theme presets to get started, while aiming to offer advanced customisation for each aspect of your UI 18 | 19 | ![tweakcn.com](public/og-image.png) 20 | 21 | ## Motivation 22 | 23 | Websites made with shadcn/ui famously look the same. tweakcn is a tool that helps you customize shadcn/ui components visually, to make your components stand-out. 24 | Currently in beta, starting with a Tailwind CSS theme editor. Support for all other shadcn/ui components is planned. 25 | 26 | ## Current Features 27 | 28 | You can find the full feature list here: https://tweakcn.com/#features 29 | 30 | ## Roadmap 31 | 32 | You can find the updated roadmap here: https://tweakcn.com/#roadmap 33 | 34 | ## Run Locally 35 | 36 | ### Prerequisites 37 | 38 | - Node.js 18+ 39 | - npm / yarn / pnpm 40 | 41 | ### Installation 42 | 43 | 1. Clone the repository: 44 | 45 | ```bash 46 | git clone https://github.com/jnsahaj/tweakcn.git 47 | cd tweakcn 48 | ``` 49 | 50 | 2. Install dependencies: 51 | 52 | ```bash 53 | npm install 54 | ``` 55 | 56 | 3. Start the development server: 57 | 58 | ```bash 59 | npm run dev 60 | ``` 61 | 62 | 4. Open [http://localhost:3000](http://localhost:3000) in your browser. 63 | 64 | ## Contributors 65 | 66 | 67 | 68 | 69 | 70 | Made with [contrib.rocks](https://contrib.rocks). 71 | 72 | ### Interested in Contributing? 73 | 74 | Contributions are welcome! Please feel free to submit a Pull Request. 75 | 76 | # Star History 77 | 78 |

79 | 80 | 81 | 82 | GitHub Star History for jnsahaj/tweakcn 83 | 84 | 85 |

86 | -------------------------------------------------------------------------------- /app/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jnsahaj/tweakcn/b94822b72a152a789615c6d9fb03baa64990d34e/app/apple-touch-icon.png -------------------------------------------------------------------------------- /app/editor/theme/page.tsx: -------------------------------------------------------------------------------- 1 | import { getEditorConfig } from "@/config/editors"; 2 | import { cn } from "@/lib/utils"; 3 | import Editor from "@/components/editor/editor"; 4 | import { Metadata } from "next"; 5 | import { Header } from "../../../components/editor/header"; 6 | 7 | export const metadata: Metadata = { 8 | title: "tweakcn — Theme Generator for shadcn/ui", 9 | description: 10 | "Easily customize and preview your shadcn/ui theme with tweakcn. Modify colors, fonts, and styles in real-time.", 11 | }; 12 | 13 | export default function Component() { 14 | return ( 15 | <> 16 |
21 |
22 |
23 | 24 |
25 |
26 | 27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /app/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jnsahaj/tweakcn/b94822b72a152a789615c6d9fb03baa64990d34e/app/favicon-16x16.png -------------------------------------------------------------------------------- /app/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jnsahaj/tweakcn/b94822b72a152a789615c6d9fb03baa64990d34e/app/favicon-32x32.png -------------------------------------------------------------------------------- /app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jnsahaj/tweakcn/b94822b72a152a789615c6d9fb03baa64990d34e/app/favicon.ico -------------------------------------------------------------------------------- /app/not-found.tsx: -------------------------------------------------------------------------------- 1 | export default function NotFound() { 2 | return ( 3 |
4 |

404

5 |

Page not found

6 |
7 | ); 8 | } 9 | -------------------------------------------------------------------------------- /app/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useEffect, useState } from "react"; 4 | import { Header } from "@/components/home/header"; 5 | import { Hero } from "@/components/home/hero"; 6 | import { ThemePresetSelector } from "@/components/home/theme-preset-selector"; 7 | import { Features } from "@/components/home/features"; 8 | import { HowItWorks } from "@/components/home/how-it-works"; 9 | import { Roadmap } from "@/components/home/roadmap"; 10 | import { FAQ } from "@/components/home/faq"; 11 | import { CTA } from "@/components/home/cta"; 12 | import { Footer } from "@/components/home/footer"; 13 | 14 | export default function Home() { 15 | const [isScrolled, setIsScrolled] = useState(false); 16 | const [mobileMenuOpen, setMobileMenuOpen] = useState(false); 17 | 18 | useEffect(() => { 19 | const handleScroll = () => { 20 | if (window.scrollY > 10) { 21 | setIsScrolled(true); 22 | } else { 23 | setIsScrolled(false); 24 | } 25 | }; 26 | 27 | window.addEventListener("scroll", handleScroll); 28 | return () => window.removeEventListener("scroll", handleScroll); 29 | }, []); 30 | 31 | return ( 32 |
33 |
38 |
39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 |
47 |
49 | ); 50 | } 51 | -------------------------------------------------------------------------------- /assets/discord.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/github.svg: -------------------------------------------------------------------------------- 1 | 9 | 11 | 12 | -------------------------------------------------------------------------------- /assets/heart.svg: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /assets/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /assets/og-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jnsahaj/tweakcn/b94822b72a152a789615c6d9fb03baa64990d34e/assets/og-image.png -------------------------------------------------------------------------------- /assets/twitter.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /components/editor/code-panel-dialog.tsx: -------------------------------------------------------------------------------- 1 | import { Dialog, DialogContent } from "@/components/ui/dialog"; 2 | import CodePanel from "./code-panel"; 3 | import { ThemeEditorState } from "@/types/editor"; 4 | 5 | interface CodePanelDialogProps { 6 | open: boolean; 7 | onOpenChange: (open: boolean) => void; 8 | themeEditorState: ThemeEditorState; 9 | } 10 | 11 | export function CodePanelDialog({ 12 | open, 13 | onOpenChange, 14 | themeEditorState, 15 | }: CodePanelDialogProps) { 16 | return ( 17 | 18 | 19 |
20 | 21 |
22 |
23 |
24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /components/editor/color-picker.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect, useMemo } from "react"; 2 | import { Label } from "@/components/ui/label"; 3 | import { ColorPickerProps } from "@/types"; 4 | import { debounce } from "@/utils/debounce"; 5 | 6 | const ColorPicker = ({ color, onChange, label }: ColorPickerProps) => { 7 | const [isOpen, setIsOpen] = useState(false); 8 | const [localColor, setLocalColor] = useState(color); 9 | 10 | // Update localColor if the prop changes externally 11 | useEffect(() => { 12 | setLocalColor(color); 13 | }, [color]); 14 | 15 | // Create a stable debounced onChange handler 16 | const debouncedOnChange = useMemo( 17 | () => debounce((value: string) => onChange(value), 20), 18 | [onChange] 19 | ); 20 | 21 | const handleColorChange = (e: React.ChangeEvent) => { 22 | const newColor = e.target.value; 23 | setLocalColor(newColor); 24 | debouncedOnChange(newColor); 25 | }; 26 | 27 | // Cleanup debounced function on unmount 28 | useEffect(() => { 29 | return () => { 30 | debouncedOnChange.cancel(); 31 | }; 32 | }, [debouncedOnChange]); 33 | 34 | return ( 35 |
36 |
37 | 43 |
44 |
45 |
setIsOpen(!isOpen)} 49 | > 50 | 57 |
58 | 64 |
65 |
66 | ); 67 | }; 68 | 69 | export default ColorPicker; 70 | -------------------------------------------------------------------------------- /components/editor/control-section.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { ChevronDown, ChevronUp } from "lucide-react"; 3 | import { cn } from "@/lib/utils"; 4 | import { ControlSectionProps } from "@/types"; 5 | 6 | const ControlSection = ({ 7 | title, 8 | children, 9 | expanded = false, 10 | className, 11 | id, 12 | }: ControlSectionProps) => { 13 | const [isExpanded, setIsExpanded] = useState(expanded); 14 | 15 | return ( 16 |
20 |
setIsExpanded(!isExpanded)} 23 | > 24 |

{title}

25 | 36 |
37 | 38 |
44 |
{children}
45 |
46 |
47 | ); 48 | }; 49 | 50 | export default ControlSection; 51 | -------------------------------------------------------------------------------- /components/editor/css-import-dialog.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { 3 | Dialog, 4 | DialogContent, 5 | DialogDescription, 6 | DialogFooter, 7 | DialogHeader, 8 | DialogTitle, 9 | } from "@/components/ui/dialog"; 10 | import { Button } from "@/components/ui/button"; 11 | import { Textarea } from "@/components/ui/textarea"; 12 | import { Alert, AlertDescription } from "@/components/ui/alert"; 13 | import { AlertCircle } from "lucide-react"; 14 | 15 | interface CssImportDialogProps { 16 | open: boolean; 17 | onOpenChange: (open: boolean) => void; 18 | onImport: (css: string) => void; 19 | } 20 | 21 | const CssImportDialog: React.FC = ({ 22 | open, 23 | onOpenChange, 24 | onImport, 25 | }) => { 26 | const [cssText, setCssText] = useState(""); 27 | const [error, setError] = useState(null); 28 | 29 | const handleImport = () => { 30 | // Basic validation - check if the CSS contains some expected variables 31 | if (!cssText.trim()) { 32 | setError("Please enter CSS content"); 33 | return; 34 | } 35 | 36 | try { 37 | // Here you would add more sophisticated CSS parsing validation 38 | // For now we'll just do a simple check 39 | if (!cssText.includes("--") || !cssText.includes(":")) { 40 | setError( 41 | "Invalid CSS format. CSS should contain variable definitions like --primary: #color" 42 | ); 43 | return; 44 | } 45 | 46 | onImport(cssText); 47 | setCssText(""); 48 | setError(null); 49 | onOpenChange(false); 50 | } catch { 51 | setError("Failed to parse CSS. Please check your syntax."); 52 | } 53 | }; 54 | 55 | const handleClose = () => { 56 | setCssText(""); 57 | setError(null); 58 | onOpenChange(false); 59 | }; 60 | 61 | return ( 62 | 63 | 64 | 65 | 66 | Import Custom CSS 67 | 68 | 69 | Paste your CSS file below to customize the theme colors. Make sure 70 | to include variables like --primary, --background, etc. 71 | 72 | 73 | 74 | {error && ( 75 | 76 | 77 | {error} 78 | 79 | )} 80 | 81 |
82 |