On This Page
It builds on your laptop. It fails the second it runs on the CI runner, or inside the Docker image you deploy from, or on a teammate's machine that isn't a Mac. Same commit, same package.json, same @use line you've stared at for ten minutes because the file is right there in the sidebar.
That gap between works here and dies there is the whole story of Error: Can't find stylesheet to import. It's Dart Sass's one and only error message for "I looked, and I didn't find what you asked for," and it covers at least four unrelated problems that all happen to print identical text. I spent the better part of a day chasing this on a design-system build that passed locally and failed on every single CI run, and the eventual cause wasn't even in the causes list I'd started with. This is the writeup I wish had existed at the time.
what dart sass prints right before your build gives up
Here's the message, verbatim, from a real sass CLI run:
Error: Can't find stylesheet to import.
╷
1 │ @use "button";
│ ^^^^^^^^^^^^^
╵
main.scss 1:1 root stylesheetExit code 65. No file path guessed, no "did you mean," no suggestion. Just the line that failed and a note about where the root stylesheet was. If the failing @use is buried a few files deep, reached through a chain of @forward statements, you get a short stack of locations instead of one, and that stack turns out to matter more than it looks like it does. More on that shortly.
One thing worth knowing before you start debugging: if you're compiling to a file (not to stdout), Dart Sass writes the CSS anyway, with the error rendered as a comment and a fake content rule that puts the error text on the page. That's --error-css, and it defaults to true for file output. It exists so editor live-preview setups can show something. But it means a broken build still produces a .css file, and if your pipeline only checks "did a file get written" instead of the actual exit code, this exact error can ship to production disguised as CSS.
four files, no framework, same crash
The minimal repro is almost insultingly small. Two files:
scss
/* main.scss */
@use "button";
.card {
color: red;
}No _button.scss anywhere near it. Compile it with plain Dart Sass, no bundler involved:
bash
npm install sass
npx sass main.scss out.cssError: Can't find stylesheet to import.
╷
1 │ @use "button";
│ ^^^^^^^^^^^^^
╵
main.scss 1:1 root stylesheetThat's cause one, the boring one: the file genuinely doesn't exist, or the path is wrong. If your case were that simple you wouldn't still be reading. The other three causes produce the identical message with a file that does exist, which is where this gets annoying.
which sass, which bundler, which flag
This isn't a browser bug, so there's no Baseline table here: the resolution algorithm is Dart Sass's own, and it behaves the same on every OS the compiler runs on. What actually varies is which build tool is doing the compiling, because each one wraps Dart Sass's import resolution differently.
| Setup | Resolves node_modules bare imports on its own? | Understands pkg: / package.json "sass" exports? | Tilde (~pkg) imports? |
|---|---|---|---|
Dart Sass CLI / sass-embedded directly | No, needs --load-path | Only with --pkg-importer=node | No, treated as a literal folder name |
Vite (css.preprocessorOptions.scss) | No, needs loadPaths | Not automatically; you can wire importers: [new NodePackageImporter()] yourself | No |
webpack + sass-loader | Yes, via webpack's own resolver | Not via the "sass" exports condition specifically, but webpack's resolver often still lands on a package's main/style field | Deprecated, still works |
| Next.js (default, webpack-based) | Yes, inherits sass-loader's behavior | Same as webpack | Deprecated, still works |
node-sass | N/A. Package is dead: last published mid-2024, and its own README says it's unsupported. Don't start a project on it. |
Versions used for everything in this article: sass 1.105.0, sass-embedded 1.105.0, sass-loader 17.0.1, Vite 8.3.1, sass-migrator 2.6.1, all current as of this writing. The pkg: importer scheme itself shipped in Dart Sass 1.71.0, back in February 2024, so it's been available for a while; the reason nobody uses it is that almost nothing turns it on by default.
One-line forward-compat note: none of this changes when @import is finally removed in a future Dart Sass 3.0. That deprecation is about the old @import syntax going away; this error is about @use (and @import, while it still works) not finding a target, and it survives the migration untouched.
the one-line reason your import didn't resolve
Strip away the bundler-specific wrapping and it's one sentence: Dart Sass tried every path it's willing to try for that string, and none of them existed on disk with that exact name, that exact case, and a syntax it recognizes. "Every path it's willing to try" is doing a lot of work in that sentence, and which paths those are depends entirely on which of the four causes you've actually got.
how @use actually finds a file on disk
For a plain relative @use "button" with no bundler involved, Dart Sass tries, in the directory of the importing file: _button.scss, button.scss, _button.sass, button.sass, then the same four names inside a button/ folder (looking for an index file). It does not care whether the source uses SCSS or the indented syntax on either side: I imported a .sass partial from a .scss file during testing and it resolved without complaint, so "wrong syntax" is not actually a cause here, whatever old blog posts imply.
Two things happen if you get unlucky with the filename matching. First, if both _card.scss and card.scss exist in the same folder, you get a different error, It's not clear which file to import., not this one. Second, and this is the one that actually costs people a day: filesystem case sensitivity. macOS and Windows default to case-insensitive filesystems. Linux, meaning your CI runner, your Docker base image, and your production server, does not. @use "button" resolving to a file actually named _Button.scss is a silent no-op locally and a hard failure everywhere else. I reproduced this on a plain Linux box: a file named _Button.scss, an import written as @use "button", and the exact "Can't find stylesheet to import" error, first try, no bundler in the loop at all. It's the same as a locker combination that only opens if you enter it in exactly the right order. Close doesn't count, and macOS has been quietly rounding "close" up to "correct" for you the whole time.
node_modules imports add a second wrinkle. Bare Dart Sass and Vite do not search node_modules unless you tell them to, with --load-path / loadPaths. webpack's sass-loader does, because it hands every @use to webpack's own module resolver, which already knows about node_modules. That's the entire reason a @use "bootstrap" that works in a webpack app throws this error the moment the same code runs through Vite with no load path configured.
which file is dart sass actually complaining about?
I lost real time on two wrong guesses before landing on the actual cause, and both are worth naming so you don't repeat them.
Wrong guess one: I'd copied a @import "~some-package"; line out of an old webpack config into a project that compiles with plain Dart Sass through Vite. The tilde is a webpack/sass-loader convention for "look in node_modules," and it means nothing to Dart Sass itself. It just gets treated as a literal folder named ~some-package. With @use this actually throws a clearer, different error (The default namespace "~some-package" is not a valid Sass identifier.), which is at least honest about what's wrong. With @import, though, it fails silently into our exact error, because ~some-package is simply a path that doesn't exist. I switched from @use to @import thinking I'd fixed the namespace complaint, and instead just traded one wrong guess for the error I'd started with.
Wrong guess two: I wondered whether this was secretly the ambiguous-import error in disguise, so I deliberately created both _theme.scss and theme.scss in the same folder to see what would happen. That produces It's not clear which file to import., a real, distinct, verified error message, which told me my actual failure wasn't an ambiguity problem at all. Useful to rule out, wrong theory.
The thing that actually found it was reading the full trace instead of skimming it. When the broken @use is buried behind a @forward chain, Dart Sass prints every hop:
Error: Can't find stylesheet to import.
╷
1 │ @use "buttons";
│ ^^^^^^^^^^^^^^
╵
_components.scss 1:1 @forward
_layout.scss 1:1 @use
main.scss 1:1 root stylesheetThe instinct is to look at the last line, because that's the file you actually edited. Don't. The top frame (_components.scss here) is the file that holds the broken statement. main.scss and _layout.scss are just the chain of files that led there. That distinction is the whole difference between fixing the right file in ten seconds and grepping the wrong one for twenty minutes.
Once you know which file and which string is failing, this finds the mismatch in one shot:
bash
# Replace "button" with whatever string is in the failing @use/@import.
name="button"
echo "case-sensitive match (what Sass actually needs):"
find . -not -path "*/node_modules/*" \
\( -name "_${name}.scss" -o -name "${name}.scss" \
-o -name "_${name}.sass" -o -name "${name}.sass" \) -print
echo "case-insensitive match (what's actually on disk):"
find . -not -path "*/node_modules/*" \
\( -iname "_${name}.scss" -o -iname "${name}.scss" \
-o -iname "_${name}.sass" -o -iname "${name}.sass" \) -printNothing in either list: the file genuinely isn't there, or it's outside every load path you've configured, so check node_modules next. Something only in the second list: case mismatch, and now you know exactly which file to rename. Ranking the four causes by how often they're actually the culprit, worst to best surprise: case sensitivity first (because it's invisible until a Linux machine touches your code), missing node_modules load path second, leftover tilde syntax third, and a plain typo a distant fourth. Typos usually get caught by the editor's own file-not-found squiggle before you ever run the build.
fixing it, cause by cause
Plain typo or wrong relative path. Fix the string. Not interesting, but it's most people's actual answer, so check it first.
Case mismatch. Rename the file to match the import (or vice versa), then verify the rename actually happened. On macOS specifically, git mv Button.scss button.scss can silently no-op if core.ignorecase is true (the default there) and git decides nothing changed, so confirm git status actually shows the rename, or force it with an intermediate name: git mv Button.scss tmp.scss && git mv tmp.scss button.scss.
Missing node_modules load path. Point Dart Sass at it:
bash
npx sass --load-path=node_modules src/main.scss dist/out.cssIn Vite:
js
// vite.config.js
export default {
css: {
preprocessorOptions: {
scss: {
loadPaths: ['node_modules'],
},
},
},
}Leftover tilde import. Drop the ~. @use "~bootstrap" becomes @use "bootstrap" once a load path can see node_modules; the tilde was never doing anything for Dart Sass to begin with.
an import setup that survives a case-sensitive server
The fixes above patch one broken import. The better move is removing the whole class of bug. For anything pulled from node_modules, use Dart Sass's own package resolution instead of load paths and hope:
json
// node_modules/your-design-system/package.json
{
"exports": {
".": { "sass": "./src/scss/index.scss" }
}
}scss
@use "pkg:your-design-system";bash
npx sass --pkg-importer=node src/main.scss dist/out.cssI don't love that this needs its own flag (it should probably be the default by now), but once it's on, pkg: imports resolve the same way regardless of which bundler is running, which is exactly the property load paths don't have. Vite takes the equivalent option directly, since css.preprocessorOptions.scss passes straight through to the modern Sass JS API:
js
// vite.config.js
import { NodePackageImporter } from 'sass'
export default {
css: {
preprocessorOptions: {
scss: { importers: [new NodePackageImporter()] },
},
},
}For your own files, the actual fix for the case-sensitivity class of bug isn't a Sass setting at all. It's a naming rule. Lowercase, hyphenated filenames, no exceptions, enforced somewhere other than memory:
bash
# fails if any tracked stylesheet has an uppercase character in its name
git ls-files -- '*.scss' '*.sass' | grep -P '[A-Z]' && exit 1 || exit 0catching this before it reaches a linux box
- Run the actual Sass build in CI on the same OS you deploy to, and fail the job on a non-zero exit code, not just on missing output, since
--error-cssmeans a broken build can still produce a file that looks fine at a glance. - Add the lowercase-filename check above as a pre-commit hook or a CI step; it costs a
findcommand and catches the single most confusing cause in this whole article before it leaves a laptop. - Centralize
loadPaths/importersin one shared config object imported byvite.config.js, any webpack config, and any standalonesassCLI scripts, instead of hand-typing the same array three times and letting them drift. - If you're mid-migration off
@import(see the separate article on that warning if you haven't done this yet), do it after this error is already sorted.@use's stricter namespace rules catch the tilde-import mistake with a clearer message than@importever will, so migrating actually shrinks this problem's surface area, not just the deprecation noise.
what to check first, in order
Run the file with sass path/to/file.scss directly, no bundler, and read every line of the trace: the top frame, not the bottom one, names the actual broken statement. Then run the case-insensitive find above against exactly the string in that statement. If it turns up a match your case-sensitive search missed, rename the file and move on, that's most of these. If both searches come up empty, it's a load path problem for anything under node_modules, or a genuine typo for anything else. And if you're deploying anywhere Linux-based, which is most places, treat "works on my Mac" as informative, not reassuring.
CODELZ Newsletter
Join the newsletter to receive the latest updates in your inbox.