A Vite plugin that allows you to control where CSS stylesheets are injected in your React or Vue application. Perfect for scenarios where you need precise control over style placement, especially when working with Shadow DOM.
- Custom CSS positioning - Place stylesheets exactly where you need them in your component tree
- Shadow DOM support - Ideal for Shadow DOM implementations where styles need to be scoped
- Component-level lazy-loading - Optionally inject each (code-split) component's CSS only when it loads
- Development mode - Optional hot module replacement support
npm install vite-plugin-css-positionAdd the plugin to your vite.config.ts:
...
import { viteCssPosition } from "vite-plugin-css-position";
export default defineConfig({
plugins: [react(), /* or vue(), */ viteCssPosition({ mode: "injectPerChunk" /* or cssChunks */ })],
});Import and place the StylesTarget component where you want your styles to be injected:
import StylesTarget from "vite-plugin-css-position/react";
export function App() {
return (
<div>
<StylesTarget />
<span>Your App Content</span>
</div>
);
}<script setup lang="ts">
import StylesTarget from "vite-plugin-css-position/vue";
</script>
<template>
<div>
<StylesTarget />
</div>
</template>Your stylesheets will now be injected at the position of the <StylesTarget /> component.
The plugin accepts optional configuration:
viteCssPosition({
enableDev: true,
mode: "cssChunks",
});instanceId- A custom identifier for the plugin instance. Useful when you have multiple instances or need to avoid conflicts. Defaults to a random UUID.enableDev- Whentrue, enables CSS injection during development mode. Defaults tofalse. Enable this for HMR support.mode- How CSS is delivered and registered. Defaults to"inject". See Modes below.cssChunksStrategy- Only used whenmode: "cssChunks"."link"(default) renders<link rel="stylesheet">;"adopt"fetches the CSS and applies it viaadoptedStyleSheets. See Modes.jsAssetsFilterFunction- Filter function(chunk) => booleanto control which JS output chunk(s) receive the CSS injection code. Useful with multiple entry points.
mode controls how stylesheets reach the StylesTarget position:
mode |
CSS delivery | Rendered as | Lazy-loading |
|---|---|---|---|
"inject" (default) |
all CSS inlined into the entry JS | <style> |
no — loaded up front |
"injectPerChunk" |
each chunk's CSS inlined into its JS | <style> |
yes — per code-split chunk |
"cssChunks" |
Vite's emitted .css files are kept |
<link> / adoptedStyleSheets |
yes — per code-split chunk |
"inject" is the original behavior and fully backward compatible. The per-chunk modes require
build.cssCodeSplit (Vite's default; forced on automatically).
mode only affects the production build. In dev (enableDev: true) CSS is always injected
per-module for HMR.
The cssChunksStrategy option chooses how the CSS is included:
"link"(default) — renders<link rel="stylesheet">. Simplest, but note that a<link>inside a Shadow DOM is not render-blocking, so a brief flash of unstyled content (FOUC) is possible while it loads."adopt"— fetches the CSS file and applies it viaadoptedStyleSheets. No FOUC, deduplicated across multiple shadow roots, and CSP-ideal. Requiresfetchand a modern browser (Chrome 73+ / Firefox 101+ / Safari 16.4+);
No code changes required — the default mode: "inject" behaves exactly like 2.0.9.
What's new in 3.0.0:
- New
modeoption:"injectPerChunk"and"cssChunks"add component-level lazy-loading;"cssChunks"keeps Vite's emitted.cssfiles (see Modes). - Zero runtime dependencies — the CSS-by-JS injection is now built in.
See the CHANGELOG for details.
# Install dependencies
npm install
# Run the playground
npm run play
# Build the library
npm run buildThe built-in CSS-by-JS injection is a trimmed, vendored port of
vite-plugin-css-injected-by-js
by Marco Prontera (MIT License).
MIT © Alexander Bogoslawski