Gathering detailed insights and metrics for react-shadow-scope
Gathering detailed insights and metrics for react-shadow-scope
Gathering detailed insights and metrics for react-shadow-scope
Gathering detailed insights and metrics for react-shadow-scope
npm install react-shadow-scope
Typescript
Module System
Node Version
NPM Version
TypeScript (89.22%)
CSS (8.63%)
JavaScript (2.15%)
Total Downloads
0
Last Day
0
Last Week
0
Last Month
0
Last Year
0
MIT License
9 Stars
170 Commits
3 Forks
1 Watchers
4 Branches
1 Contributors
Updated on Apr 18, 2025
Latest Version
2.0.3
Package Id
react-shadow-scope@2.0.3
Unpacked Size
143.36 kB
Size
31.52 kB
File Count
7
NPM Version
11.2.0
Node Version
22.11.0
Published on
Mar 31, 2025
Cumulative downloads
Total Downloads
Last Day
0%
NaN
Compared to previous day
Last Week
0%
NaN
Compared to previous week
Last Month
0%
NaN
Compared to previous month
Last Year
0%
NaN
Compared to previous year
25
1<Scope stylesheet={styles}>
Traditional global CSS risks naming collisions, specificity conflicts, and unwanted style inheritance. Modern tools have been designed to solve these problems by using emulated encapsulation, but nothing can protect from inherited styles except for shadow DOM.
This package does not burden you with all the boilerplate around shadow DOM, nor force you to use web components. Did you know you can attach a shadow root to regular elements, like a <div>
? That's essentially what react-shadow-scope
does behind the curtain.
Note This package supports Tailwind in the shadow DOM via the
<Tailwind>
component. Using Tailwind globally risks naming collisions with other utility classes. This can be especially important for library authors.
As a rule of thumb, you should limit your global CSS to little or nothing. The native @scope
rule can get you pretty far, but it still doesn't protect from inherited styles. Shadow DOM encapsulation is the single best tool we have.
npm i react-shadow-scope
To create a new CSS scope, import the Scope
component from the package and just pass a string to the stylesheet
prop.
1import { Scope } from 'react-shadow-scope'; 2 3const MyComponent = () => ( 4 <> 5 {/* Scope gives you protection from inherited styles! */} 6 <style>{`#Demo h1 { color: blue; text-decoration: underline; }`}</style> 7 <div id="Demo"> 8 <h1>This title is blue with underline</h1> 9 <Scope stylesheet={`h1 { color: red; }`}> 10 <h1>This title is red without underline</h1> 11 <Scope stylesheet={`h1 { font-style: italic; }`}> 12 <h1>This title is italicized without underline or color</h1> 13 </Scope> 14 </Scope> 15 </div> 16 </> 17);
By default, <Scope>
renders a <react-shadow-scope>
element, but doesn't define it in the custom element registry. The custom tag name just avoids cases where <div>
or <span>
would break HTML validation.
This can be overridden via the tag
prop, in case of conflicts or for better legibility in the dev tools.
1<Scope tag="my-element">
The above will output: <my-element>
If you're using TypeScript, you will need to merge with the interface where these element types are declared.
1import { CustomIntrinsicElement } from 'react-shadow-scope'; 2 3declare global { 4 namespace ReactShadowScope { 5 interface CustomElements { 6 'my-element': CustomIntrinsicElement; 7 } 8 } 9}
Note
In some cases, HTML requires certain nesting rules to be valid. For example,
<ul>
may only contain<li>
tags as direct children. To work around this, you can either render all<li>
tags in one parent<Scope>
, or apply your own semantics withrole="list"
androle="listitem"
to your markup instead of using<ul>
and<li>
.
It's normally a good idea to contain the complexity of your design. In other words, instead of designing the different use cases outside a component, design from the inside by describing the use case, like usedFor="page"
or importance="urgent"
. The goal should be to eliminate the need for consumers of your component to write any CSS at all.
However, sometimes it's necessary to compose styles from the parent scope. In such an event, you may add a class to the <Scope>
component and select that to style the outer element. Although React applies some unique rules to custom elements, you can just use className
as usual and we'll forward it to class
internally.
This is a gray area that has some indirect impact on the shadow DOM via the cascade. You can also selectively reach into the shadow DOM with shadow parts. It's important to be aware this breaks encapsulation, so it's generally not the recommended approach. Although it's sometimes necessary or beneficial, it often isn't, so be careful. It may require shifting your mental model a bit at first.
This package borrows from normalize.css to make style defaults more consistent across browsers. This feature is opted-in by default to hopefully save you some hassle, but you can opt-out any time by setting the normalize
prop to false.
1<Scope stylesheet={styles} normalize={false}>
All normalized styles are contained inside a @layer
called normalize
, which gives them the lowest priority, making them easy to override.
Note
By default,
<Scope>
appliesdisplay: contents;
to avoid problems with layouts. (This preserves accessibility because it lacks semantics to interfere with anyway.) You may override this with:host { /* overrides */ }
.
Runtime overhead is a common criticism of CSS-in-JS approaches, but react-shadow-scope
solves these performance concerns using constructed style sheets.
Using the css
tagged template literal, you can create a new CSSStyleSheet
object and pass it to the stylesheet
prop. This allows you to share a single style sheet across different components and between each instance, rather than continuously duplicating the styles at runtime.
1import { css } from 'react-shadow-scope'; 2 3const stylesheet = css` 4 h1 { 5 color: red; 6 } 7`; 8 9const MyComponent = () => { 10 return ( 11 <Scope stylesheet={stylesheet}> 12 <h1>title here</h1> 13 </Scope> 14 ); 15}; 16 17const MyOtherComponent = () => { 18 return ( 19 <Scope stylesheet={stylesheet}> 20 <h1>another title here</h1> 21 </Scope> 22 ); 23};
NOTE: On the server,
css
returns a string which is rendered in a<style>
tag to support SSR styles while avoiding "flash-of-unstyled-content" (FOUC) issues. This string will be converted to aCSSStyleSheet
object after hydration, at which point it will share only a single style sheet between all components.
For better HMR support, react-shadow-scope
exports a hook (useCSS
) that returns a unique tagged template function. Using the hook will allow you to make updates to the stylesheet on the fly.
You should create a Symbol
outside the component function, then pass it to the useCSS
hook. This will uniquely identify a single reference so the resulting css
function will always update the same CSSStyleSheet
. While it still works without a unique key, it will create duplicate style sheets, which will become a performance concern.
1const key = Symbol(); 2 3const MyComponent = () => { 4 const css = useCSS(key); 5 return ( 6 <Scope 7 stylesheet={css` 8 h1 { 9 color: red; 10 } 11 `} 12 > 13 <h1>title here</h1> 14 </Scope> 15 ); 16};
Note
When using a key, you should NOT use the resulting
css
function multiple times, because the same reference is shared between each function call. This means the last result will override all previous results. If you need multiple stylesheets, consider callinguseCSS
multiple times with different keys.1const key1 = Symbol(); 2const key2 = Symbol(); 3 4const MyComponent = () => { 5 const css1 = useCSS(key1); 6 const css2 = useCSS(key2); 7 ... 8}
To use multiple stylesheets, you can also use the stylesheets
prop (plural) and pass an array.
1<Scope stylesheets={[theme, styles]}>
To support server components, you must directly pass a plain string to the stylesheet
or adoptedStyleSheets
props. This is because Constructed Style Sheets are only supported on the client, and any attempt to use css
or useCSS
on the server will result in an error.
1const MyComponent = () => { 2 return ( 3 <Scope 4 stylesheet={ 5 /* css */ ` 6 h1 { 7 color: red; 8 } 9 ` 10 } 11 > 12 <h1>title here</h1> 13 </Scope> 14 ); 15};
While both the usual css
function and a directly-provided string will render a <style>
tag on the server and convert to a CSSStyleSheet
on the client, there is a key difference to watch out for:
A plain string will create a duplicate CSSStyleSheet
for each instance, so keep in mind the trade-offs when deciding between a server and client component.
If you'd rather save static assets, or you depend on a third-party stylesheet, you can pass a (relative or absolute) URL to the href
prop.
1<Scope href="/mystyles.css">
When rendering on the server, this will simply add a <link>
tag pointing to the given href.
Once the remote stylesheet has loaded, it will be cloned into a constructable CSSStyleSheet
and cached by href, so they won't be loaded (or constructed) multiple times even if they were loaded by a different <Scope>
.
You can also link multiple stylesheets using the hrefs
(plural) prop.
1<Scope hrefs={['/theme.css', '/mystyles.css']}>
When linking external stylesheets, server-rendered components will appear as expected on the first paint. Client rendered components, however, would have a FOUC issue if not for some extra care. While the styles are busy loading on the client, we apply :host { visibility: hidden; }
by default. These styles can be customized as well, and will only apply while the fetch promise is pending.
1<Scope href="/mystyles.css" pendingStyles={css` 2 :host { 3 display: block; 4 opacity: 0.3; 5 } 6`}>
Most of the time, you won't want the children to be rendered in the same CSS scope as the component. In such a case, you will want to use <slot>
tags and pass children to the slottedContent
prop.
1<Scope stylesheet={styles} slottedContent={children}> 2 <slot></slot> 3</Scope>
This is just an abstraction over shadow DOM, so anything you can do with shadow DOM, you can do with slottedContent
, including named slots and so on.
But at the point you're taking full advantage these additional features, you may be entering territory where it becomes more practical to just use the bare syntax of declarative shadow DOM... which you can also do with this package!
When you use shadow DOM, form elements inside the shadow root can't naturally connect to forms outside of it. This means features like form validation and submission won't work by default. The FormControl
component solves this problem by creating a custom element that acts as the form control.
1import { FormControl } from 'react-shadow-scope'; 2 3// A basic text input 4<FormControl 5 tag="x-input" 6 stylesheets={[theme, css``]} 7 control="text" 8 value={value} 9 onInput={handleInput} 10> 11 <input type="text" /> 12</FormControl> 13 14// A checkbox 15<FormControl 16 tag="x-checkbox" 17 stylesheets={[theme, css``]} 18 control="checkbox" 19 name="agree" 20 checked={checked} 21 onChange={handleChange} 22> 23 <input type="checkbox" /> 24</FormControl> 25 26// A submit button 27<FormControl 28 tag="x-button" 29 stylesheets={[theme, css``]} 30 control="button" 31 type="submit" // default when nested in a form 32> 33 <button>Submit</button> 34</FormControl>
Note While you can build your own custom controls using this API, it's recommended to nest a corresponding input with a matching type (or tag) inside. The library will keep the inner control in sync with its host element automatically.
The FormControl
component accepts different configurations based on the type of control:
Common Controls: text
, password
, email
, tel
, url
, search
1<FormControl 2 tag="x-input" 3 stylesheets={[theme, css``]} 4 control="text" // or any text type 5 value={string} // current value 6 name={string} // field name 7 placeholder={string} // optional placeholder text 8 required={boolean} // is the field required? 9 readonly={boolean} // is the field read-only? 10 disabled={boolean} // is the field disabled? 11/>
Checkbox/Radio: checkbox
, radio
1<FormControl 2 tag="x-checkbox" 3 stylesheets={[theme, css``]} 4 control="checkbox" // or 'radio' 5 checked={boolean} // is it checked? 6/>
Number/Range/Date: number
, range
, date
, time
, datetime-local
, month
, week
1<FormControl 2 tag="x-number-input" 3 stylesheets={[theme, css``]} 4 control="number" // or any range/date type 5 value={string} // current value 6 min={string | number} // minimum value 7 max={string | number} // maximum value 8 step={string | number} // step increment 9/>
Buttons: <button>
, image
1<FormControl 2 tag="x-button" 3 stylesheets={[theme, css``]} 4 control="button" // or image 5 type="button" // or submit, reset 6/>
And so on, including hidden
, <select>
, <textarea>
, file
, color
,
The form control automatically:
All while maintaining shadow DOM encapsulation for your styles.
If you want to use declarative shadow DOM directly, without the <Scope>
component, you can use <Template>
as you usually would natively. This adds support to React for the native <template>
element, with some added features.
1import { useCSS, Template, CustomElement } from 'react-shadow-scope'; 2 3const MyComponent = () => { 4 const css = useCSS(); 5 return ( 6 <card-element> 7 {/* Note the declarative `adoptedStyleSheets` prop! */} 8 <Template 9 shadowrootmode="closed" 10 adoptedStyleSheets={[ 11 css` 12 /* styles here */ 13 `, 14 ]} 15 > 16 <h1> 17 <slot name="heading">(Untitled)</slot> 18 </h1> 19 <slot>(No content)</slot> 20 </Template> 21 <span slot="heading">Title Here</span> 22 <p>Inside Default Slot</p> 23 </card-element> 24 ); 25};
Tailwind support is already built-in so you don't have to roll your own solution. Just install and set up the Tailwind package as usual, and this package will encapsulate it in the shadow DOM.
1<Tailwind slottedContent={children}> 2 <h1 className="text-slate-900 font-extrabold text-4xl"> 3 Hello from the Shadow DOM! 4 </h1> 5 <slot></slot> 6</Tailwind>
Note
Your output CSS file should be in the
/public
folder (or wherever your static assets are served from.) The expected filename istailwind.css
by default, but can be customized (see next section).Be sure to remove tailwind from the
<link>
tag in your HTML. You may want to add this in its place:1<style> 2 body { 3 margin: 0; 4 line-height: inherit; 5 } 6</style>
href
- This is /tailwind.css
if omitted. This will be fetched once and cached.customStyles
- Pass a string or CSSStyleSheet
(the css
tagged template function is recommended)pendingStyles
- Works the same as pendingStyles
on the <Scope>
component.slottedContent
- Works the same as slottedContent
on the <Scope>
component.MIT © 2023 Jonathan DeWitt
No vulnerabilities found.
No security vulnerabilities found.