Smart Class SearchCopy a class list out of Chrome DevTools, paste it into the sidebar or the
Command Palette, and jump straight to the component that renders it - even
when your codebase spreads those classes across A plain text/regex search for the DevTools string will never find any of these. Smart Class Search will. What's New in v0.4.11
See CHANGELOG.md for the full history. Examples
Local variable resolution
Template literal + ternary
How it worksYou can search from the sidebar or the Command Palette - both share the same index, so pick whichever fits the moment. Sidebar (stays open, updates as you type)
Command Palette (one-off, self-closing)
Editor context menu (search what you highlighted)Select a class list in any open file, right-click, and pick Smart Class Search
or Smart Class Search: Replace Class.... The selection pre-fills the input —
it takes priority over the clipboard, and Run "Smart Class Search: Find Duplicate Components" from the Command Palette any time to look for elements in different files that render with an identical set of classes. Always-On Adaptive Text SearchSearch now automatically runs class matching alongside a full source-text search (comments, JSX text, property names, strings) by default. The ranking engine intelligently adapts based on the query:
Class Replacement (Interactive Preview & Selective Apply)You can replace classes across your workspace using either the sidebar or the Command Palette: Via the Sidebar (Recommended):
Via the Command Palette:
Smart paste detectionBoth the sidebar and the Command Palette accept any of the following - no manual trimming required:
Supported Syntax & PatternsThe extension statically analyzes your files to extract class names from complex, programmatic code structures:
Markup files (
|
| Syntax | Frameworks |
|---|---|
class="p-4 flex" / class='p-4 flex' |
HTML, Blade, ERB, Twig |
:class="{ 'bg-red-500': isError }" |
Vue |
v-bind:class="isOpen ? 'rounded-lg' : ''" |
Vue |
x-bind:class="'shadow-md'" |
Alpine |
class={isOpen ? "shadow-md" : "shadow-none"} |
Svelte, Astro |
class:bg-base-200={dark} |
Svelte |
class:list={["gap-2", isActive && "items-center"]} |
Astro |
<script> blocks in .vue/.svelte and .astro frontmatter are parsed as real JS/TS, so a
const styles = cn("p-4", isBig && "mb-12") in the script half resolves exactly as it would in a
.tsx file, and its classes are reported at their true line in the .vue/.astro file.
Stylesheets (.css, .scss, .sass, .less)
Class selectors (.btn-primary, .card) and the utilities inside Tailwind @apply rules
are indexed, so a class defined or composed in a stylesheet is found alongside the components that
use it:
.btn-primary {
@apply px-4 py-2 rounded-lg bg-blue-600;
}
[!NOTE] Limitations:
- Variable resolution only follows assignments within the same file. Classes imported from a different file/module and referenced by name (e.g.,
import { styles } from "./styles") still can't be resolved, since this is static single-file analysis rather than full cross-module data-flow tracking.- In markup, only quoted strings inside a dynamic binding are read as classes. An unquoted object key (Vue's
:class="{ active: isOn }") and Blade's@class([...])helper are not extracted.- Replacing inside a markup file rewrites class attributes and Svelte
class:directives; a class living inside a.vue/.svelte<script>cn()call is handled by the plain-text fallback instead.
Indexing & performance
The extension activates on onStartupFinished, so the index is already built by
the time you first search — you don't have to open the sidebar to warm it up.
That event fires after VS Code has finished restoring the window, so it never
delays window open. Parsing runs in background worker threads (up to half your
CPU cores, max 4), so a full build never blocks the extension host or other
extensions.
On activation, the extension loads a persisted index cache (a file in the
extension's workspace storage) and compares file modification times. Only
files that have changed since the last session are re-parsed, so large repos
skip the full scan on every restart. The cache is invalidated automatically
when the include or exclude settings change.
When no cache exists (first run), the extension scans the workspace once
(default: JS/TS, the markup extensions, and stylesheets listed above, excluding
node_modules, .next, dist, build, coverage, .git, out) and builds an in-memory index of
class → file → locations. After that, a FileSystemWatcher keeps the
index current incrementally - only the file that changed gets re-parsed. You
can force a full rebuild with "Smart Class Search: Rebuild Index".
While a build runs, the status bar spins and the sidebar shows "Indexing files…". To keep builds fast:
- Files larger than 1MB and
*.min.*files are skipped, since they are bundles or vendor code rather than components. - Files that can't contain a class (a quick text check finds no
className,style, class helper, array, orclassattribute) skip the full parse. - A file that fails to parse stays in the index with no classes, so it is still reachable by text search and isn't re-parsed on every start. The reason is logged to the Smart Class Search output channel.
Text search keeps each indexed file's contents in memory (loaded during the build), so searching never touches the disk. Files that don't contain every significant query term are rejected with one substring check before any line-by-line scoring.
Settings
| Setting | Default | Description |
|---|---|---|
smartClassLookup.include |
**/*.{ts,tsx,js,jsx,mjs,cjs,mts,cts,vue,svelte,astro,html,htm,php,erb,twig,hbs,css,scss,sass,less} |
Files to index |
smartClassLookup.exclude |
**/{node_modules,.next,dist,build,coverage,.git,out}/** |
Files/folders to skip |
smartClassLookup.minScore |
0.3 |
Minimum match score (0–1) to show a result |
smartClassLookup.maxResults |
25 |
Max number of ranked results shown |
smartClassLookup.enablePreview |
true |
Live preview of the file when navigating results (Quick Pick & Sidebar) |
smartClassLookup.duplicateMinClasses |
3 |
Minimum shared classes for "Find Duplicate Components" to report a group |
Developing
npm install
npm run test # tsc to out/ + runs the extractor/matcher smoke tests
npm run bundle # esbuild to dist/extension.js + dist/parsePool.js — what the extension actually loads
npm run watch # the same bundle in watch mode, for the Extension Development Host
npm run typecheck # tsc --noEmit
The extension entry point is the esbuild bundle at dist/extension.js, so run
npm run bundle (or npm run watch) at least once before pressing F5.
dist/parsePool.js is the parse worker, bundled separately because worker
threads load it by path. The out/ tree produced by npm run compile exists
only so the smoke tests can require each module in isolation.
To try it in VS Code: open this folder, press F5 to launch an Extension
Development Host with the extension loaded, open any project that uses
Tailwind (React, Vue, Svelte, Astro, plain HTML, …) in that window, and run
"Smart Class Search" from the Command Palette.