Configuration
Defaults for a subtree through GrConfigProvider, the order they resolve in, auto-import and the runtime adapter.
Machine-translated, not yet reviewed. Read the original
Configuration here has two layers and they do not overlap. At build time you
configure what reaches the CSS — that is uno.config.ts and the
installation page. At runtime you configure how
components behave by default — and that is GrConfigProvider, which this whole
page is about.
Defaults for a subtree
<GrConfigProvider
size="sm"
:component-defaults="{ GrButton: { variant: 'outline' }, GrInput: { clearable: true } }"
>
<App />
</GrConfigProvider>The provider renders transparently (display: contents) and works through
provide/inject, so it changes no layout and can sit anywhere — including
nested several times over. A child provider merges over its parent at the prop
level, not at the component block: override one variant and you do not lose
the rest of the GrButton defaults.
The resolution order
- A local prop on the component itself.
componentDefaults[Component]of the nearest provider.- The provider’s global
size. - The component’s own default.
Which gives a rule worth knowing as a consumer too: a prop configurable through
the provider is declared with a default of undefined. Otherwise Vue would
substitute its value before the component ever looked into the config, and the
provider would quietly stop working.
The effective config can be read from the application with useGrConfig() — that
is public API, not an internal.
Two size scales
The provider’s size is about controls: xs | sm | md | lg. Overlays have a
scale of their own — sm | md | lg | xl | full — and the global size does not
touch it, because xs means nothing for a modal window. Window size is set
pointwise:
<GrConfigProvider :component-defaults="{ GrModal: { size: 'lg' } }">That is how GrModal, GrDialog, GrConfirmDialog, GrPromptDialog,
GrCommandPalette and GrDrawer are configured.
A theme for a subtree
theme puts its value into data-theme on the wrapper. Themes are declared with
an attribute selector, so a dark island inside a light application works with no
extra styles.
Overlay panels teleport into body and live outside the wrapper in the DOM — but
in the component tree they stay inside, so inject reaches them and they set the
theme on themselves. The modal, the drawer, the dropdown, the popover, the
tooltip, the selects, the toaster and the image viewer are all covered.
The document theme is a different job: useTheme and initThemeEarly do it,
see theming. The package has no two mechanisms for one thing,
and the provider’s prop is about the island.
The layering scale and the mount point
zIndexBase recomputes --gr-z-* from a base (dropdown +0, tooltip +50,
modal +100, toast +200) and sets them on <html>, restoring the previous
values on unmount. On :root rather than on the wrapper — for the same reason
panels set their own theme: a panel moves into body and never sees the
subtree’s variables. There is one scale per document, and a second provider with
a different base warns about the conflict in a dev build.
The same result is four lines of CSS. The prop is for where the base comes from at runtime — a micro-frontend inside somebody else’s application, for instance.
portalTarget names the container the subtree’s overlays move into. By default
that is the shared #gr-portal in body, which the package creates itself on
first open.
The provider only names the target — it does not create the DOM, and the
container has to exist by the time an overlay opens. There is one requirement for
it, and it is strict: no transform, filter, contain, perspective or
will-change. Each of them creates a containing block for position: fixed,
and the floating panels start measuring from the container instead of the
viewport.
Auto-import
The recommended way for a production application is the resolver for
unplugin-vue-components. It inserts static imports on the package’s subpaths
into the SFC, so tree-shaking stays tight: the bundle gets exactly what appeared
in the templates.
yarn add -D @feugene/unplugin-granularity unplugin-vue-componentsimport Components from 'unplugin-vue-components/vite'
import { GranularityResolver } from '@feugene/unplugin-granularity'
export default defineConfig({
plugins: [vue(), Components({ resolvers: [GranularityResolver()] })],
})No CSS needs wiring up for this: most components have no CSS of their own, and the ones that do carry it inside their chunk, so it arrives with the import.
The core resolver is greedy — it claims any name starting with Gr. Companion
package resolvers work from an explicit list and are therefore registered
before it: GranularityChronoResolver() first, GranularityResolver() last.
The runtime adapter
The resolver scans templates. Directives applied in render, JSX or TSX are
invisible to it — createGranularity is for those:
import { createApp } from 'vue'
import { createGranularity } from '@feugene/granularity/vue'
import { GrButton } from '@feugene/granularity/components/GrButton'
import { vHotkey } from '@feugene/granularity/directives'
import App from './App.vue'
createApp(App)
.use(createGranularity({
components: [GrButton],
directives: [{ name: 'hotkey', directive: vHotkey }],
}))
.mount('#app')The adapter imports no component itself — it takes them as an argument. The bundle gets exactly what you passed and nothing beyond. The flip side of the same property: passing “everything” here means losing granularity, and tree-shaking will not save you, because the import was yours.
Beyond components and directives the adapter takes provides and
globalProperties — one convenient entry point for a bootstrap file instead of a
scattering of app.use(...).