Gathering detailed insights and metrics for @fluentui/merge-styles
Gathering detailed insights and metrics for @fluentui/merge-styles
Fluent UI web represents a collection of utilities, React components, and web components for building web applications.
npm install @fluentui/merge-styles
Typescript
Module System
Node Version
NPM Version
99.7
Supply Chain
99.1
Quality
89.1
Maintenance
100
Vulnerability
100
License
@fluentui/react-monaco-editor v1.7.288
Published on 31 Jan 2025
@fluentui/react-docsite-components v8.13.170
Published on 31 Jan 2025
@fluentui/react-charting v5.23.50
Published on 31 Jan 2025
@fluentui/react-monaco-editor v1.7.287
Published on 30 Jan 2025
@fluentui/react-docsite-components v8.13.169
Published on 30 Jan 2025
@fluentui/react-charting v5.23.49
Published on 30 Jan 2025
TypeScript (95.72%)
JavaScript (1.81%)
MDX (1.6%)
SCSS (0.51%)
HTML (0.2%)
CSS (0.15%)
Shell (0.01%)
EJS (0.01%)
Total Downloads
23,041,809
Last Day
31,157
Last Week
149,597
Last Month
672,092
Last Year
8,311,479
18,800 Stars
18,997 Commits
2,762 Forks
289 Watching
95 Branches
897 Contributors
Minified
Minified + Gzipped
Latest Version
8.6.13
Package Id
@fluentui/merge-styles@8.6.13
Unpacked Size
1.42 MB
Size
250.96 kB
File Count
290
NPM Version
10.8.1
Node Version
20.16.0
Publised On
08 Aug 2024
Cumulative downloads
Total Downloads
Last day
-8.8%
31,157
Compared to previous day
Last week
-17.9%
149,597
Compared to previous week
Last month
0%
672,092
Compared to previous month
Last year
36.2%
8,311,479
Compared to previous year
The merge-styles
library provides utilities for loading styles through javascript. It is designed to make it simple to style components through javascript. It generates css classes, rather than using inline styling, to ensure we can use css features like pseudo selectors (:hover) and parent/child selectors (media queries).
The library was built for speed and size; the entire package is 2.62k gzipped. It has no dependencies other than tslib
.
Simple usage:
1import { mergeStyles, mergeStyleSets } from '@fluentui/merge-styles'; 2 3// Produces 'css-0' class name which can be used anywhere 4mergeStyles({ background: 'red' }); 5 6// Produces a class map for a bunch of rules all at once 7mergeStyleSets({ 8 root: { background: 'red' }, 9 child: { background: 'green' }, 10}); 11 12// Returns { root: 'root-0', child: 'child-1' }
Both utilities behave similar to a deep Object.assign; you can collapse many objects down into one class name or class map.
The basic idea is to provide tools which can take in one or more css styling objects representing the styles for a given element, and return a single class name. If the same set of styling is passed in, the same name returns and nothing is re-registered.
Defining rules at runtime has a number of benefits over traditional build time staticly produced css:
Only register classes that are needed, when they're needed, reducing the overall selector count and improving TTG.
Dynamically create new class permutations based on contextual theming requirements. (Use a different theme inside of a DIV without downloading multiple copies of the css rule definitions.)
Use JavaScript to define the class content (using utilities like color converters, or reusing constant numbers becomes possible.)
Allow control libraries to merge customized styling in with their rules, avoiding complexities like css selector specificity.
Simplify RTL processing; lefts become rights in RTL, in the actual rules. No complexity like html[dir=rtl]
prefixes necessary, which alleviates unexpected specificity bugs. (You can use /* noflip */
comments to avoid flipping if needed.)
Reduce bundle size. Automatically handles vendor prefixing, unit providing, RTL flipping, and margin/padding expansion (e.g. margin will automatically expand out to margin TRBL, so that we avoid specificity problems when merging things together.)
Reduce the build time overhead of running through CSS preprocessors.
TypeScript type safety; spell "background" wrong and get build breaks.
In static solutions, there is very little runtime evaluation required; everything is injected as-is. Things like auto prefixing and language specific processing like sass mixins are all evaluated at build time.
In runtime styling, much of this is evaluated in the browser, so you are paying a cost in doing this. However, with performance optimizations like memoization, you can minimize this quite a bit, and you gain all of the robustness enumerated above.
The api surfaces consists of 3 methods and a handful of interfaces:
mergeStyles(..args[]: IStyle[]): string
- Takes in one or more style objects, merges them in the right order, and produces a single css class name which can be injected into any component.
mergeStyleSets(...args[]: IStyleSet[]): { [key: string]: string }
- Takes in one or more style set objects, each consisting of a set of areas, each which will produce a class name. Using this is analogous to calling mergeStyles for each property in the object, but ensures we maintain the set ordering when multiple style sets are merged.
concatStyleSets(...args[]: IStyleSet[]): IStyleSet
- In some cases you simply need to combine style sets, without actually generating class names (it is costs in performance to generate class names.) This tool returns a single set merging many together.
concatStyleSetsWithProps(props: {}, ...args[]: IStyleSet[]): IStyleSet
- Similar to concatStyleSet
except that style sets which contain functional evaluation of styles are evaluated prior to concatenating.
Example:
1const result = concatStyleSetsWithProps<IFooProps, IFooStyles>( 2 { foo: 'bar' }, 3 (props: IFooProps) => ({ root: { background: props.foo } }), 4 (props: IFooProps) => ({ root: { color: props.foo } }), 5);
A style object represents the collection of css rules, except that the names are camelCased rather than kebab-cased. Example:
1let style = { 2 backgroundColor: 'red', 3 left: 42, 4};
Additionally, style objects can contain selectors:
1let style = { 2 backgroundColor: 'red', 3 ':hover': { 4 backgroundColor: 'blue'; 5 }, 6 '.parent &': { /* parent selector */ }, 7 '& .child': { /* child selector */ } 8};
A style set represents a map of area to style object. When building a component, you need to generate a class name for each element that requires styling. You would define this in a style set.
1let styleSet = { 2 root: { background: 'red' }, 3 button: { margin: 42 }, 4};
When building a component, you will need a style set map of class names to inject into your elements' class attributes.
The recommended pattern is to provide the classnames in a separate function, typically in a separate file ComponentName.classNames.ts
.
1import { IStyle, mergeStyleSets } from '@fluentui/merge-styles'; 2 3export interface IComponentClassNames { 4 root: string; 5 button: string; 6 buttonIcon: string; 7} 8 9export const getClassNames = (): IComponentClassNames => { 10 return mergeStyleSets({ 11 root: { 12 background: 'red', 13 }, 14 15 button: { 16 backgroundColor: 'green', 17 }, 18 19 buttonIcon: { 20 margin: 10, 21 }, 22 }); 23};
The class map can then be used in a component:
1import { getClassNames } from './MyComponent.classNames'; 2 3export const MyComponent = () => { 4 let { root, button, buttonIcon } = getClassNames(); 5 6 return ( 7 <div className={root}> 8 <button className={button}> 9 <i className={buttonIcon} /> 10 </button> 11 </div> 12 ); 13};
Custom selectors can be defined within IStyle
definitions:
1{ 2 background: 'red', 3 ':hover': { 4 background: 'green' 5 } 6}
By default, the rule will be appended to the current selector scope. That is, in the above scenario, there will be 2 rules inserted when using mergeStyles
:
1.css-0 { 2 background: red; 3} 4.css-0:hover { 5 background: green; 6}
In some cases, you may need to use parent or child selectors. To do so, you can define a selector from scratch and use the &
character to represent the generated class name. When using the &
, the current scope is ignored. Example:
1{ 2 // selector relative to parent 3 '.ms-Fabric--isFocusVisible &': { 4 background: 'red' 5 } 6 7 // selector for child 8 '& .child' { 9 background: 'green' 10 } 11}
This would register the rules:
1.ms-Fabric--isFocusVisible .css-0 { 2 background: red; 3} 4.css-0 .child { 5 background: green; 6}
While we suggest avoiding global selectors, there are some cases which make sense to register things globally. Keep in mind that global selectors can't be guaranteed unique and may suffer from specificity problems and versioning issues in the case that two different versions of your library get rendered on the page.
To register a selector globally, wrap it in a :global()
wrapper:
1{ 2 ':global(button)': { 3 overflow: 'visible' 4 } 5}
Media queries can be applied via selectors. For example, this style will produce a class which has a red background when above 600px, and green when at or below 600px:
1mergeStyles({ 2 background: 'red', 3 '@media(max-width: 600px)': { 4 background: 'green', 5 }, 6 '@supports(display: grid)': { 7 display: 'grid', 8 }, 9});
Produces:
1.css-0 { 2 background: red; 3} 4 5@media (max-width: 600px) { 6 .css-0 { 7 background: green; 8 } 9} 10 11@supports (display: grid) { 12 .css-0 { 13 display: grid; 14 } 15}
One important concept about mergeStyleSets
is that it produces a map of class names for the given elements:
1mergeStyleSets({
2 root: { background: 'red' }
3 thumb: { background: 'green' }
4});
Produces:
1.root-0 { 2 background: red; 3} 4.thumb-1 { 5 background: green; 6}
In some cases, you may need to alter a child area by interacting with the parent. For example, when the parent is hovered, change the child background. We recommend using global, non-changing static classnames to target the parent elements:
1const classNames = { 2 root: 'Foo-root', 3 child: 'Foo-child', 4}; 5 6mergeStyleSets({ 7 root: [classNames.root, { background: 'lightgreen' }], 8 9 child: [ 10 classNames.child, 11 { 12 [`.${classNames.root}:hover &`]: { 13 background: 'green', 14 }, 15 }, 16 ], 17});
The important part here is that the selector does not have any mutable information. In the example above,
if classNames.root
were dynamic, it would require the rule to be re-registered when it mutates, which
would be a performance hit.
By default when using mergeStyles
, class names that are generated will use the prefix css-
followed by a number, creating unique rules where needed. For example, the first class name produced will be 'css-0'.
When using mergeStyleSets
, class names automatically use the area name as the prefix.
Merging rules like:
1mergeStyleSets({ a: { ... }, b: { ... } })
Will produce the class name map:
1{ a: 'a-0', b: 'b-1' }
If you'd like to override the default prefix in either case, you can pass in a displayName
to resolve this:
1{ 2 displayName: 'MyComponent', 3 background: 'red' 4}
This generates:
1.MyComponent-0 { 2 background: red; 3}
Style objects can be represented by a simple object, but also can be an array of the objects. The merge functions will handle arrays and merge things together in the given order. They will also ignore falsey values, allowing you to conditionalize the results.
In the following example, the root class generated will be different depending on the isToggled
state:
1export const getClassNames = (isToggled: boolean): IComponentClassNames => { 2 return mergeStyleSets({ 3 root: [ 4 { 5 background: 'red', 6 }, 7 isToggled && { 8 background: 'green', 9 }, 10 ], 11 }); 12};
By default, nearly all of the major rtl-sensitive CSS properties will be auto flipped when the dir="rtl" flag is present on the HTML
tag of the page.
There are some rare scenarios (linear-gradients, etc) which are not flipped, for the sake of keeping the bundle size to a minimum. If there are missing edge cases, please submit a PR to address.
In rare condition where you want to avoid auto flipping, you can annotate the rule with the @noflip
directive:
1mergeStyles({ 2 left: '42px @noflip', 3});
Resolving the class names on every render can be an unwanted expense especially in hot spots where things are rendered frequently. To optimize, we recommend 2 guidelines:
For your getClassNames
function, flatten all input parameters into simple immutable values. This helps the memoizeFunction
utility to cache the results based on the input.
Use the memoizeFunction
function from the @fluentui/utilities
package to cache the results, given a unique combination of inputs. Example:
1import { memoizeFunction } from '@fluentui/utilities'; 2 3export const getClassNames = memoizeFunction((isToggled: boolean) => { 4 return mergeStyleSets({ 5 // ... 6 }); 7});
Registering font faces example:
1import { fontFace } from '@fluentui/merge-styles'; 2 3fontFace({ 4 fontFamily: `"Segoe UI"`, 5 src: `url("//cdn.com/fontface.woff2) format(woff2)`, 6 fontWeight: 'normal', 7});
Note that in cases like fontFamily
you may need to embed quotes in the string as shown above.
Registering animation keyframes example:
1import { keyframes, mergeStyleSets } from '@fluentui/merge-styles'; 2 3let fadeIn = keyframes({ 4 from: { 5 opacity: 0, 6 }, 7 to: { 8 opacity: 1, 9 }, 10}); 11 12export const getClassNames = () => { 13 return mergeStyleSets({ 14 root: { 15 animationName: fadeIn, 16 }, 17 }); 18};
By default merge-styles
will initially inject a style
element into the document head as the first node and then append and new style
elements as next sibling to the previous one added.
In some cases you may want to control where styles are injected to ensure some stylesheets are more specific than others. To do this, you can add a placeholder style
element in the head with data-merge-styles
attribute:
1<head> 2 <style data-merge-styles></style> 3</head>
Merge styles will ensure that any generated styles are added after the placeholder.
You can import renderStatic
method from the /lib/server
entry to render content and extract the css rules that would have been registered, as a string.
Example:
1import { renderStatic } from '@fluentui/merge-styles/lib/server'; 2 3let { html, css } = renderStatic(() => { 4 return ReactDOM.renderToString(...); 5});
Caveats for server-side rendering:
For example:
1const rootClass = mergeStyles({ background: 'red' }); 2const App = () => <div className={rootClass} />; 3 4// App will render, but "rootClass" is a string which won't get re-evaluated in this call. 5renderStatic(() => ReactDOM.renderToString(<App/>);
Using memoizeFunction
around rule calculation can help with excessive rule recalc performance overhead.
Rehydration on the client may result in mismatched rules. You can apply a namespace on the server side to ensure there aren't name collisions.
Some content security policies prevent style injection without a nonce. To set the nonce used by merge-styles
:
1Stylesheet.getInstance().setConfig({
2 cspSettings: { nonce: 'your nonce here' },
3});
If you're working inside a Fluent UI React app (formerly Office UI Fabric React), this setting can also be applied using the global window.FabricConfig.mergeStyles.cspSettings
. Note that this must be set before any Fluent UI React code is loaded, or it may not be applied properly.
1window.FabricConfig = { 2 mergeStyles: { 3 cspSettings: { nonce: 'your nonce here' }, 4 }, 5};
merge-styles
has support for shadow DOM. This feature is opt-in and incrementally adoptable. To enable the feature you need to include two React Providers:
MergeStylesRootProvider
: acts as the "global" context for your application. You should have one of these per page.MergeStylesShadowRootProvider
: a context for each shadow root in your application. You should have one of these per shadow root.merge-styles
does not provide an option for creating shadow roots in React as how you get a shadow root doesn't matter, just that you have a reference to one. react-shadow
is one library that can create shadow roots in React and will be used in examples.
1import { PrimaryButton } from '@fluentui/react'; 2import { MergeStylesRootProvider, MergeStylesShadowRootProvider } from '@fluentui/utilities'; 3import root from 'react-shadow'; 4 5const ShadowRoot = ({ children }) => { 6 // This is a ref but we're using state to manage it so we can force 7 // a re-render. 8 const [shadowRootEl, setShadowRootEl] = React.useState<HTMLElement | null>(null); 9 10 return ( 11 <MergeStylesRootProvider> 12 <root.div className="shadow-root" delegatesFocus ref={setShadowRootEl}> 13 <MergeStylesShadowRootProvider shadowRoot={shadowRootEl?.shadowRoot}>{children}</MergeStylesShadowRootProvider> 14 </root.div> 15 </MergeStylesRootProvider> 16 ); 17}; 18 19<ShadowRoot> 20 <PrimaryButton>I'm in the shadow DOM!</PrimaryButton> 21</ShadowRoot> 22<PrimaryButton>I'm in the light DOM!</PrimaryButton>
You do not need to update your merge-styles
styles to support shadow DOM but you can make styles more efficient with some updates.
Shadow DOM support is achieved in merge-styles
by using constructable stylesheets and is scoped by "stylesheet keys". merge-styles
creates one stylesheet per key and in Fluent this means each component has its own stylesheet. Each MergeStylesShadowRootProvider
will only adopt styles for components it contains plus the global sheet (we cannot be certain whether we need this sheet or not so we always adopt it). This means a MergeStylesShadowRootProvider
that contains a button will only adopt button styles (plus the global styles) but not checkbox styles, making styling within the shadow root more efficient.
If you use customizable
or styled
the existing "scope" value provided to these functions is used a unique key. If no key is provided merge-styles
falls back to a "global" key. This global key is a catch-all and allows us to support code that was written before shadow DOM support was added or code that is called outside of React context.
All @fluentui/react
styles are scoped via customizable
and styled
(and some updates to specific component styles where needed). If your components use these functions and you set the "scope" property your components will automatically be scoped.
If you're using mergeStyles()
(and other merge-styles
APIs) directly, your styles will be placed in the global scope and still be available in shadow roots, just not as optimally as possible.
1import { useMergeStylesHooks } from '@fluentui/react'; 2import { mergeStyles } from '@fluentui/merge-styles'; 3import type { ShadowConfig } from '@fluentui/merge-styles'; 4 5// This must be globally unique for the application 6const MY_COMPONENT_STYLESHEET_KEY: string = 'my-unique-key'; 7 8const MyComponent = props => { 9 const { useWindow, useShadowConfig, useAdoptedStylesheet } = useMergeStylesHooks(); 10 11 // Make sure multi-window scenarios work (e.g., pop outs) 12 const win: Window = useWindow(); 13 const shadowConfig: ShadowConfig = useShadowConfig(MY_COMPONENT_STYLESHEET_KEY, win); 14 15 const styles = React.useMemo(() => { 16 // shadowConfig must be the first parameter when it is used 17 return mergeStyles(shadowConfig, myStyles); 18 }, [shadowConfig, myStyles]); 19 20 useAdoptedStylesheet(MY_COMPONENT_STYLESHEET_KEY); 21};
No vulnerabilities found.
Reason
30 commit(s) out of 30 and 22 issue activity out of 30 found in the last 90 days -- score normalized to 10
Reason
no vulnerabilities detected
Reason
update tool detected
Details
Reason
security policy file detected
Details
Reason
license file detected
Details
Reason
no binaries found in the repo
Reason
GitHub code reviews found for 28 commits out of the last 30 -- score normalized to 9
Details
Reason
branch protection is not maximal on development and all release branches
Details
Reason
dependency not pinned by hash detected -- score normalized to 5
Details
Reason
no badge detected
Reason
dangerous workflow patterns detected
Details
Reason
non read-only tokens detected in GitHub workflows
Details
Reason
project is not fuzzed
Score
Last Scanned on 2022-08-15
The Open Source Security Foundation is a cross-industry collaboration to improve the security of open source software (OSS). The Scorecard provides security health metrics for open source projects.
Learn More