Note
A TailwindPlus account is required. The site serves nothing under /plus without a session, so
every run logs in.
A downloader for TailwindPlus components (HTML, React, Vue) across Tailwind CSS v3 and v4 in system, light, and dark modes. Includes a diff tool to compare any two formats, between downloads or within one.
Download to a single JSON file (default) or directory tree of components, for multiple use-cases.
npx github:richardkmichael/tailwindplus-downloader#latestTakes about a minute. You will be prompted for TailwindPlus credentials; the session is saved automatically for re-use.
Output is written to tailwindplus-components-[TIMESTAMP].json in the current directory.
npx requires no installation. A browser is needed only to log in, so Playwright Chromium and its
system dependencies are required for a first run, and for any run whose saved session has expired.
To use the agent skill, clone the repo and symlink, or copy the
contrib/tailwind-plus/ directory from GitHub:
git clone https://github.com/richardkmichael/tailwindplus-downloaderWith a clone, in-repo commands are also available:
npx twp-downloadernpx twp-diffnpx twp-create-skeleton
Both JSON and directory output formats default to a timestamped destination.
Downloads all components to a single JSON file. See Data Format.
Use with the TailwindPlus MCP server, use the
agent skill, or query directly with jq.
# → tailwindplus-components-[TIMESTAMP].json
npx github:richardkmichael/tailwindplus-downloader#latest
npx github:richardkmichael/tailwindplus-downloader#latest --output ./twp.jsonDownloads each component snippet as an individual file in a directory tree. See Data Format.
Agents can discover and read components using CLI tools (ls, cat, etc.) without loading the full
JSON into context.
# → tailwindplus-components-[TIMESTAMP]/
npx github:richardkmichael/tailwindplus-downloader#latest --output-format=dir
npx github:richardkmichael/tailwindplus-downloader#latest --output-format=dir --output=./twpEventually the saved session will expire and you will be prompted for credentials again. To avoid prompting, save credentials as a JSON file:
echo '{"email": "your-email@example.com", "password": "your-password"}' > .tailwindplus-downloader-credentials.jsonTip
Use the TailwindPlus MCP server. It uses the JSON file from the downloader.
Ask for a component:
> I need a simple one-line search input to put in the app header.
mcp-tailwindplus - Get Component by Full Name (MCP)(full_name: "Application UI.Forms.Input Groups.Input with leading icon", framework: "react", tailwind_version: "4")
⎿ {
"version": "2025-07-14-204017",
"full_name": "Application UI.Forms.Input Groups.Input with leading icon",
… +15 lines (ctrl+r to expand)
mcp-tailwindplus - Get Component by Full Name (MCP)(full_name: "Application UI.Forms.Input Groups.Input with keyboard shortcut", framework: "react", tailwind_version: "4")
⎿ {
"version": "2025-07-14-204017",
"full_name": "Application UI.Forms.Input Groups.Input with keyboard shortcut",
… +16 lines (ctrl+r to expand)
Perfect! Here are two ideal options for a header search input:
1. Input with Leading Icon
Application UI.Forms.Input Groups.Input with leading icon
- Simple search input with magnifying glass icon
- Just swap the EnvelopeIcon for MagnifyingGlassIcon
- Clean, compact design perfect for headers
2. Input with Keyboard Shortcut
Application UI.Forms.Input Groups.Input with keyboard shortcut
- Shows "⌘K" shortcut hint
- Modern search UX that users expect
- Great for power users
For a header, I'd recommend the first one with a search icon - it's the most recognizable and space-efficient.
A skill in contrib/tailwind-plus/ allows the agent to automatically
browse and read components from the directory output when asked to build UI. See Setup for
installation options.
Install by symlinking into a skills directory:
# Global
ln -s /path/to/tailwindplus-downloader/contrib/tailwind-plus ~/.claude/skills/tailwind-plus
# Project
ln -s /path/to/tailwindplus-downloader/contrib/tailwind-plus .claude/skills/tailwind-plusThe full JSON file is too large for LLM context. A skeleton file contains component names without
code, allowing the LLM to search names and use jq to fetch specific component code on demand via a
command execution MCP server.
# Within the repo:
npm run create-skeleton
npm run create-skeleton -- twp.json # specific file (note: -- is required by npm)
# Via npx:
npx --package=github:richardkmichael/tailwindplus-downloader#latest -- twp-create-skeleton
npx --package=github:richardkmichael/tailwindplus-downloader#latest -- twp-create-skeleton twp.jsonAdd the skeleton file as context to a coding session. Example jq query:
jq '.tailwindplus.Marketing."Page Sections"."Hero Sections"."Simple centered".snippets[] | select(.name == "html" and .version == 4) | .code' --raw-output path/to/twp.json
# Help (includes all options and debug flags)
npx github:richardkmichael/tailwindplus-downloader#latest --help
# Adjust workers (default 15)
npx github:richardkmichael/tailwindplus-downloader#latest --workers 10
# Overwrite existing output without prompting (useful in scripts)
npx github:richardkmichael/tailwindplus-downloader#latest --output=./twp --overwrite
# Custom credentials or session file
npx github:richardkmichael/tailwindplus-downloader#latest --credentials ./my-credentials.json
npx github:richardkmichael/tailwindplus-downloader#latest --session ./my-session.json
# Times to retry a page that fails to download (default 3)
npx github:richardkmichael/tailwindplus-downloader#latest --retries 5
# Print the resolved configuration and exit
npx github:richardkmichael/tailwindplus-downloader#latest --show-configWithin the repo, or with a global install, short-form aliases are available:
npx twp-downloader, npx twp-diff, npx twp-create-skeleton.
Do not change the format in the TailwindPlus web UI while a download is running. The downloader sets the account format and verifies that pages come back in it, so a change made underneath the run fails jobs with a format mismatch.
Works with the JSON single file output to compare components between downloads, and to compare one format against another. The diff tool is helpful because TailwindPlus undergoes small fixes for which there is no changelog.
# Compare two most recent downloads automatically (assumes default filename)
npx --package=github:richardkmichael/tailwindplus-downloader#latest -- twp-diff
# Compare specific files
npx --package=github:richardkmichael/tailwindplus-downloader#latest -- twp-diff --old-file old.json --new-file new.json
# Show only component names that differ
npx --package=github:richardkmichael/tailwindplus-downloader#latest -- twp-diff --names-only
# Help
npx --package=github:richardkmichael/tailwindplus-downloader#latest -- twp-diff --help--from and --to name a format outright, using the same framework-vN-mode names the downloader
uses in its output and in directory-tree filenames. Any format can be compared against any other,
across frameworks, versions and modes, and either side can come from either file.
# What a mode changes, within a single download
npx --package=github:richardkmichael/tailwindplus-downloader#latest -- twp-diff \
--file components.json --from html-v4-light --to html-v4-dark
# How two frameworks differ, within a single download
npx --package=github:richardkmichael/tailwindplus-downloader#latest -- twp-diff \
--file components.json --from html-v4-system --to vue-v4-system
# One format, between two downloads
npx --package=github:richardkmichael/tailwindplus-downloader#latest -- twp-diff \
--old-file old.json --new-file new.json --from react-v4-dark --to react-v4-dark--file reads both sides from one download. --from and --to replace --tw, --tw-from,
--tw-to and --framework, which continue to work on their own.
A format written without a mode, html-v4, names the mode-less format. eCommerce components are
downloaded that way and exist in 6 formats rather than 18, so asking a mode of them cannot match:
they are skipped, and the run reports how many were skipped and why.
- Node.js on a current LTS line — 22, 24 or 26 — and npm. Odd-numbered releases are not
long-term support lines and are not supported;
npmwarns rather than refusing, so a run on one may fail in less obvious ways. - Playwright Chromium and its system dependencies — for logging in; a run with a saved session launches no browser
- git — optional, provides better diffs (recommended)
The downloader produces a JSON file with this structure:
{
"version": "2025-07-14-235056",
"downloaded_at": "2025-07-14T23:50:56Z",
"component_count": 33,
"download_duration": "27.2s",
"downloader_version": "3.4.0",
"descriptions": {
"products": {
"Marketing": {
"description": "Heroes, feature sections, newsletter sign up forms — ...",
"pricing_description": "Heroes, feature sections, newsletter sign up forms — ..."
}
},
"subcategories": {
"Marketing.Page Sections.Hero Sections": {
"description": "Hero section examples for Tailwind CSS, designed and built ...",
"introduction": "Use these Tailwind CSS hero section examples to add ..."
}
}
},
"tailwindplus": {
"Marketing": {
"Page Sections": {
"Hero Sections": {
"Simple centered": {
"name": "Simple centered",
"snippets": [
{
"code": "<div class=\"...\">...</div>",
"language": "html",
"mode": "light",
"name": "html",
"preview": "...",
"supportsDarkMode": true,
"version": 4
},
{
"code": "<div class=\"dark:bg-gray-900...\">...</div>",
"language": "html",
"mode": "dark",
"name": "html",
"preview": "...",
"supportsDarkMode": true,
"version": 4
},
{
"code": "<div className=\"...\">...</div>",
"language": "jsx",
"mode": "light",
"name": "react",
"preview": "...",
"supportsDarkMode": true,
"version": 4
},
{
"code": "<div className=\"dark:bg-gray-900...\">...</div>",
"language": "jsx",
"mode": "dark",
"name": "react",
"preview": "...",
"supportsDarkMode": true,
"version": 4
}
]
}
}
}
}
}
}The descriptions section holds the prose TailwindPlus publishes for each product and
subcategory. It sits beside the component tree rather than on its nodes, so the tree stays a
plain name-keyed hierarchy. Subcategory keys are the dotted product.category.subcategory path
into that tree. Categories and components have no descriptions upstream.
tailwindplus-components-[TIMESTAMP]/
├── metadata.json
├── descriptions.json
└── Marketing/
└── Page Sections/
└── Hero Sections/
└── Simple centered/
├── v3/
│ ├── html-light.html
│ ├── html-dark.html
│ ├── html-system.html
│ ├── react-light.jsx
│ ├── react-dark.jsx
│ ├── react-system.jsx
│ ├── vue-light.vue
│ └── ...
└── v4/
├── html-light.html
└── ...
TailwindPlus renders each page's component data into the HTML as JSON, so the code is read out of the response rather than off the rendered page. Almost every step is a plain HTTP request.
- Prompts for credentials, if not provided, and logs in to establish a session
- Saves the session automatically, to use it on the next run -- no need to store credentials
- Reads the component hierarchy from the discovery page
- Sets the format -- framework, TailwindCSS version and mode -- with a single request
- Runs workers to fetch component pages in parallel
- Organizes the result into a hierarchy (JSON file or directory) matching the site
A browser is launched only for the login form. A run with a saved session never starts one.
This project uses ESLint for code quality control.
# Check code style
npm run lint
# Fix auto-fixable issues
npm run lint:fixCI also runs ShellCheck against the smoke-test harness, so check that before pushing changes to it:
shellcheck test/smoke-test.shUnit tests cover the pure logic -- entity decoding, page-data parsing, format selection and sorting -- and need no network.
npm run test:unitThe smoke tests cover option permutations (JSON and directory output, --overwrite, --log,
default timestamped paths, interrupts) using real downloads. Every test that downloads skips
itself unless a session or credentials file is present, leaving argument handling, the abort paths
and the diff tool to run without an account.
npm run smoke-test # smoke tests only
npm test # unit tests, then smoke testslatest floats: every final release moves it onto that release, which is what makes the #latest
install commands above resolve to the newest version. Installs re-read the tag from the remote
each time, so they always get the current one.
A clone does not. It keeps whatever latest pointed at when it was cloned, and neither git pull
nor git fetch --tags will move it -- git declines to overwrite a tag it already holds, reporting
[rejected] latest -> latest (would clobber existing tag) and still exiting 0, so it is easy to
miss. To update it:
git fetch origin --force refs/tags/latest:refs/tags/latestPrefer that to git fetch --tags --force, which force-updates every tag whose name matches one on
the remote -- including any local tag of your own that happens to share a name.