Troubleshooting
Symptom, cause, fix. Collected from the cases that actually broke rather than from imaginary ones.
On this page
- The component is grey and bare
- The borders are gone, buttons are colourless, headings are all one size
- The styles are gone for one particular component
- A translucent background arrives opaque
- The build fails on an import from the package
- The build fails with ComponentRootImportError
- The build fails with InvalidConfigError
- There is space for the icon but no icon
- TypeScript cannot find the subpath types
- Auto-import does not fire
- The dark theme flashes on load
- An overlay panel is stuck in the wrong place
- Overlays are not found in tests
- None of the above
Almost every breakage at the entrance is one of three things: a missing CSS import, a reset declared in the wrong layer, or a missing required peer dependency in the project. Below, by symptom.
The component is grey and bare
What you see. A button with no colour, no radius, no padding — as if there were no CSS at all.
Why. The sheet is not imported. The tokens, the base layer, the themes, the component styles and the utilities all arrive in a single virtual module, and it is your application that imports it, not an integration:
import './reset.css'
import 'virtual:granum.css'The borders are gone, buttons are colourless, headings are all one size
What you see. It looks like the case above, except the CSS is clearly there: text and background colours are in place, and everything the library draws is not.
Why. The reset is declared after granum’s layers and therefore wins
against the whole library. Unlayered CSS beats ANY @layer regardless of
specificity, and a layer declared later beats one declared earlier.
* { border-width: 0 } from the reset strips the borders,
h1…h6 { font-size: inherit } the heading sizes, and
button { background-color: transparent } from @unocss/reset/tailwind.css the
buttons’ background as well. tailwind-compat.css comments that line out
precisely for the sake of component libraries.
@import '@unocss/reset/tailwind-compat.css' layer(reset);The file must be imported first: a layer’s place in the cascade is fixed by its first appearance. Getting the order wrong produces neither an error nor a warning.
If the reset lives in a <style> inside a page template, the order of the links
may not be the one you expect: the bundler inserts such a block after the <link>
to the sheet. Then the order is set by declaration — a lone @layer reset; line
in a file imported before everything else.
The styles are gone for one particular component
What you see. The rest are fine, and the newly added one is bare.
Why. The component is missing from the components list. Add it — or set
'all', and then everything reaches the sheet.
By default this does not stay silent: js.guard fails the build on an import of
a component outside the selection. If you set it to 'warn' or 'off', the
symptom goes back to the old one — a bare component with no message at all.
A translucent background arrives opaque
What you see. The overlay backdrop is opaque, bg-[var(--gr-bg)]/55 fills
with a solid colour.
Why. The engine’s extraRules is off. preset-wind3 cannot apply alpha to
an arbitrary colour in brackets: it emits background-color: var(--gr-bg),
silently dropping the alpha — the class matched, the CSS is wrong, there is
no error. The engine makes up for it with one rule on top of the preset, and it
is on by default. The build stays silent, but granum doctor reports
provider-dialect-mismatch: the package was built for the
unocss/preset-wind3+granum@66 dialect, while the engine without the extra rules
speaks unocss/preset-wind3@66 — a different vocabulary, even though the class
names are the same. Dialects are described in the granum documentation
(ru,
en).
There used to be eight families of such rules — sr-only, animate-spin,
divide-*, space-*, object-* and the rest — because the gaps being filled
were preset-mini’s. In the preset-wind3 vocabulary seven of the eight are
native, and only the alpha is left to make up for.
The build fails on an import from the package
What you see.
Failed to resolve import "@floating-ui/dom"Why. The library is not installed. It is a required peer dependency: it
positions every floating panel — GrSelect, GrDropdown, GrDropdownMenu,
GrContextMenu, GrAutocomplete, GrTreeSelect, GrTooltip, GrPopover —
and everything that contains one: GrDataTable, GrPagination,
GrColorPicker, GrSidebar. An import from the package root always pulls it in.
This is a build error, not a runtime one: the dev server shows it as above, and
under vite build it reads Rolldown failed to resolve import "@floating-ui/dom".
The package keeps the library external deliberately — otherwise an application
that already uses floating-ui would end up with a second copy.
yarn add @floating-ui/domThe build fails with ComponentRootImportError
What you see.
ComponentRootImportError: Components 'GrButton', 'GrCard' are imported from the
package root '@feugene/granularity' in 'src/App.vue'. Import components from
'@feugene/granularity/components/GrButton', … or from 'virtual:granum/components'Why. A component is imported from the package root:
import { GrButton } from '@feugene/granularity'. granum’s selection only sees a
component by its subpath, while the root pulls in the whole package — in dev and in
SSR that is every one of its components. With granum 2 this is a guard error rather
than a silent breakage. Utilities, plugins, constants and types may still come from
the root — the rule only covers components.
import { GrButton } from '@feugene/granularity/components/GrButton'Or drop the import altogether and set up auto-import: the resolver inserts the subpath itself.
The build fails with InvalidConfigError
What you see.
Invalid granum config at 'appSource': unknown option — did you mean 'appSources'?Why. granum 2 checks granum.config strictly: a typo in a key, a v1 preset key
(themes.themeFiles, scan) and a token with the -- prefix in tokenOverrides or
define are errors naming the path to the field, not silently ignored settings. Fix
the field the error names: for a typo it suggests the nearest key, for a v1 key it
says where the key went.
There is space for the icon but no icon
What you see. An empty square where an icon passed as a class was expected
(icon="i-lucide-user").
Why. The class i-lucide-* is a utility, and your config generates it, not
the package. Icon rules are not part of the preset-wind3 vocabulary: without a
rule the class stays a class, the space for the icon is there, the picture is
not, and the build passes in silence.
The rule is handed to the engine factory — windEngine({ rules: [iconRule] }) —
or brought in by a provider. The difference is described on the
installation page: a rule from a provider leaves the
application’s vocabulary fingerprint alone, while a rule from the config changes
it and granum re-extracts every package’s classes. With one or two providers this
goes unnoticed.
The alternative is to pass the icon as a Vue component, and then there is nothing to configure.
The package’s own icons — the select arrow, the clear cross, the check mark, the
spinner — are compiled into dist and do not depend on your config.
TypeScript cannot find the subpath types
What you see.
Cannot find module '@feugene/granularity/components/GrButton'
or its corresponding type declarations. ts(2307)Meanwhile an import from the package root types fine — but granum rejects a component from the root as a build error, so that is no way out.
Why. "moduleResolution": "node" in tsconfig.json. The old algorithm does
not read the exports field, and the subpath types are declared only there. You
need "bundler" or "node16".
Auto-import does not fire
What you see. <GrButton> in a template turns into an unknown element and Vue
complains in the console.
Why. One of three things:
- the resolver is not wired into
unplugin-vue-components— check theresolversarray, not just the plugin itself; - the component comes from a companion package that is missing from the
application’s
package.json: the resolver takes the packages from there and the components from theirexports. A name no package exports (a typo, a component of your own) is not resolved; GranularityResolverhas a customprefixorexclude, and the name does not fit it. Case does not matter: the plugin turns<gr-button>intoGrButtonby itself.
The dark theme flashes on load
What you see. A light background for a fraction of a second, then a dark one.
Why. The server (or the static HTML) does not know the user’s choice, and the
theme attribute is set only after hydration. The cure is an inline script in
<head> before the first paint — the ready-made code is on the
SSR page.
An overlay panel is stuck in the wrong place
What you see. A dropdown panel or a modal positions itself relative to a container rather than to the window, and drifts on scroll.
Why. The container named in portalTarget has a transform, filter,
contain, perspective or will-change. Any of them creates a containing block
for position: fixed, and the panel starts measuring from the container. The
requirement for a portal container is strict and reads exactly like that: not one
of those properties.
Overlays are not found in tests
What you see. The test mounts a component, opens a panel — and does not find it in the document. No errors in the console.
Why. resetGranularityDom is not called between tests. The portal root and
the live region host are cached by their modules: after
document.body.innerHTML = '' they point at nodes outside the document, and the
next mount teleports the overlays there. The details are on the
testing page.
None of the above
If the symptom is not here, open an issue in the repository. A case that had to be worked through twice ends up on this page.