style lib inspired by @material-ui/styles
npm install @equinor/fusion-react-styles
A React styling library inspired by Material-UI's makeStyles API, built with JSS and designed for React 19 compatibility. This package provides a flexible, type-safe styling solution with support for theme-based styling, dynamic props, and scoped style isolation.
- ✅ React 19 Compatible - Built from the ground up for React 19
- ✅ Type-Safe - Full TypeScript support with intelligent type inference
- ✅ Theme Integration - Seamless integration with Fusion design system
- ✅ Scoped Styles - Prevent CSS class name collisions with StylesProvider seeds
- ✅ Dynamic Styles - Support for prop-based and theme-based styling
- ✅ Performance - Cached stylesheets and optimized JSS instance
- ✅ Nested Selectors - Full support for pseudo-selectors and nested rules
``bash`
npm install @equinor/fusion-react-stylesor
pnpm add @equinor/fusion-react-stylesor
yarn add @equinor/fusion-react-styles
- React ^18 or ^19
`tsx
import { makeStyles, ThemeProvider, theme } from '@equinor/fusion-react-styles';
const useStyles = makeStyles({
root: {
color: 'blue',
padding: '16px',
},
title: {
fontSize: '24px',
fontWeight: 'bold',
},
}, { name: 'MyComponent' });
function MyComponent() {
const classes = useStyles();
return (
function App() {
return (
);
}
`
Use makeStyles to create a styles hook that generates CSS class names. Always provide a name option for better performance and debugging:
`tsx
import { makeStyles } from '@equinor/fusion-react-styles';
const useStyles = makeStyles({
root: {
color: 'red',
padding: '16px',
},
button: {
backgroundColor: 'blue',
'&:hover': {
backgroundColor: 'darkblue',
},
},
}, { name: 'MyComponent' });
`
Call the hook inside your component to get class names:
`tsx`
function Component() {
const classes = useStyles();
return (
);
}
Styles can be functions that receive props. Pass props when calling the hook:
`tsx
interface ComponentProps {
color: string;
size: 'small' | 'large';
}
const useStyles = makeStyles({
root: (props: ComponentProps) => ({
color: props.color,
padding: props.size === 'large' ? '24px' : '12px',
}),
}, { name: 'DynamicComponent' });
function Component(props: ComponentProps) {
const classes = useStyles(props);
return
$3
Access theme values using theme functions. Theme properties are
StyleProperty instances that require using the getVariable() method to extract CSS values:`tsx
import { makeStyles, createStyles, theme } from '@equinor/fusion-react-styles';const useStyles = makeStyles((themeValue) =>
createStyles({
root: {
color: themeValue.colors.text.static_icons__default.getVariable('color'),
padding: themeValue.spacing.comfortable.medium.getVariable('padding'),
backgroundColor: themeValue.colors.ui.background__default.getVariable('color'),
},
button: {
backgroundColor: themeValue.colors.ui.background__default.getVariable('color'),
'&:hover': {
backgroundColor: themeValue.colors.ui.background__hover.getVariable('color'),
},
},
}),
{ name: 'ThemedComponent' }
);
`Important: Theme properties (colors, spacing, etc.) are
StyleProperty instances:
- Colors: Use theme.colors.*.getVariable('color') to get the color value as a CSS variable string
- Spacing: Use theme.spacing.*.getVariable('padding') to get the spacing value as a CSS variable string
- Typography: Use theme.typography..style. to access typography properties (e.g., theme.typography.heading.h4.style.fontSize)Example:
`tsx
const useStyles = makeStyles((themeValue) =>
createStyles({
container: {
// Color property - use getVariable('color')
backgroundColor: themeValue.colors.ui.background__default.getVariable('color'),
color: themeValue.colors.text.static_icons__default.getVariable('color'),
// Spacing property - use getVariable('padding')
padding: themeValue.spacing.comfortable.medium.getVariable('padding'),
marginTop: themeValue.spacing.comfortable.small.getVariable('padding'),
// Typography - access style properties directly
fontSize: themeValue.typography.paragraph.body_short.style.fontSize,
fontWeight: themeValue.typography.heading.h4.style.fontWeight,
},
}),
{ name: 'Container' }
);
`API Reference
$3
Creates a hook that generates CSS class names from style definitions.
Parameters:
-
stylesOrCreator: Style rules object or a function (theme) => StyleRules
- options.name: Required name prefix for debugging and performance. Without it, all instances share the same cache key, causing performance issues.Returns: A hook function that returns
Record where ClassKey is inferred from your style rules, providing type-safe access to class names.Examples:
`tsx
// Static styles
const useStyles = makeStyles({
root: { color: 'red' },
}, { name: 'MyComponent' });// Theme-based styles
const useStyles = makeStyles((theme) => ({
root: {
color: theme.colors.text.static_icons__default.getVariable('color'),
},
}), { name: 'ThemedComponent' });
// Dynamic styles with props
const useStyles = makeStyles({
root: (props: { color: string }) => ({
color: props.color,
}),
}, { name: 'DynamicComponent' });
`$3
Type-safe helper for creating style rules. Improves TypeScript inference for class keys, enabling type-safe access to class names.
`tsx
import { createStyles, makeStyles } from '@equinor/fusion-react-styles';const useStyles = makeStyles((theme) =>
createStyles({
root: {
color: theme.colors.text.static_icons__default.getVariable('color'),
},
button: {
padding: theme.spacing.comfortable.medium.getVariable('padding'),
},
}),
{ name: 'Component' }
);
function Component() {
const classes = useStyles();
// TypeScript will error if you try to access classes.foo - it knows the exact keys!
return
...;
}
`Benefits:
- Type-safe class key access - TypeScript knows exactly which class keys exist
- Better autocompletion in IDEs
- Catches typos at compile time
$3
Provides theme context to child components. Supports nested themes and theme composition.
Props:
-
theme: Theme object or function (outerTheme) => theme
- children: React nodesExample:
`tsx
import { ThemeProvider, theme } from '@equinor/fusion-react-styles';function App() {
return (
);
}
// Nested theme composition
function App() {
return (
({ ...outer, custom: true })}>
);
}
`$3
Hook to access the current theme from
ThemeProvider context.`tsx
import { useTheme } from '@equinor/fusion-react-styles';function Component() {
const theme = useTheme();
if (!theme) {
return No theme available;
}
return (
color: theme.colors.text.static_icons__default.getVariable('color'),
}}>
Hello
$3
Creates a new theme by merging custom properties with a base theme. Supports deep merging for nested properties like colors, typography, etc.
`tsx
import { createTheme, theme } from '@equinor/fusion-react-styles';
import type { FusionTheme } from '@equinor/fusion-react-styles';interface ExtendedTheme extends FusionTheme {
app: {
borderRadius: string;
sidebarWidth: string;
};
}
const extendedTheme = createTheme({
app: {
borderRadius: '12px',
sidebarWidth: '300px',
},
}, theme);
// Use in ThemeProvider
`$3
Creates an isolated styling scope to prevent CSS class name collisions. Critical for micro-frontends and dynamically loaded modules.
Props:
-
seed: String prefix for class names (e.g., "my-module")
- generateClassName: Optional custom class name generator
- jss: Optional custom JSS instance
- children: React nodesExample:
`tsx
import { StylesProvider } from '@equinor/fusion-react-styles';// Main app
// Dynamically loaded module
`$3
Creates a class name generator function with an optional seed prefix for isolation.
`tsx
import { createGenerateClassName, StylesProvider } from '@equinor/fusion-react-styles';const customGenerator = createGenerateClassName('my-app');
function App() {
return (
);
}
`$3
Utility for combining class names. Re-exported from the
clsx package.`tsx
import { clsx } from '@equinor/fusion-react-styles';const className = clsx(classes.root, isActive && classes.active, 'custom-class');
`Advanced Usage
$3
When building micro-frontends or dynamically loaded modules, use
StylesProvider with unique seeds to prevent CSS collisions:`tsx
import { StylesProvider, makeStyles, ThemeProvider, theme } from '@equinor/fusion-react-styles';// Shared styles
const sharedStyles = {
root: { color: 'blue' },
};
// Module A
const useStylesA = makeStyles(sharedStyles, { name: 'ModuleA' });
function ModuleA() {
const classes = useStylesA();
return
Module A;
}// Module B
const useStylesB = makeStyles(sharedStyles, { name: 'ModuleB' });
function ModuleB() {
const classes = useStylesB();
return
Module B;
}function App() {
return (
{/ Each module gets its own scope /}
);
}
`Even though both modules use the same styles object, they'll generate different class names:
- Module A:
module-a-makeStyles-root-1
- Module B: module-b-makeStyles-root-1$3
Full support for CSS-in-JS nested selectors:
`tsx
import { clsx } from '@equinor/fusion-react-styles';const useStyles = makeStyles({
root: {
color: 'blue',
'&:hover': {
color: 'red',
},
'& .child': {
padding: '10px',
},
'&$disabled': {
opacity: 0.5,
},
},
disabled: {},
}, { name: 'NestedComponent' });
function Component() {
const classes = useStyles();
const [disabled, setDisabled] = useState(false);
return (
Child element
);
}
`$3
Use TypeScript to ensure type safety with dynamic styles and theme:
`tsx
import { makeStyles, createStyles, theme } from '@equinor/fusion-react-styles';interface StyleProps {
color: string;
}
const useStyles = makeStyles((themeValue) =>
createStyles({
root: (props: StyleProps) => ({
...themeValue.typography.paragraph.body_short.style,
color: props.color,
padding: themeValue.spacing.comfortable.medium.getVariable('padding'),
backgroundColor: themeValue.colors.ui.background__default.getVariable('color'),
}),
}),
{ name: 'MyComponent' }
);
function Component(props: StyleProps) {
const classes = useStyles(props);
return
Styled component;
}
`$3
Extend the Fusion theme with custom properties:
`tsx
import { createTheme, theme, makeStyles } from '@equinor/fusion-react-styles';
import type { FusionTheme, StyleDefinition } from '@equinor/fusion-react-styles';interface AppTheme extends StyleDefinition {
app: {
borderRadius: string;
sidebarWidth: string;
};
}
const extendedTheme = createTheme({
app: {
borderRadius: '12px',
sidebarWidth: '300px',
},
});
// Use extended theme with makeStyles
const useStyles = makeStyles((themeValue: typeof extendedTheme) =>
createStyles({
root: {
borderRadius: themeValue.app.borderRadius,
padding: themeValue.spacing.comfortable.medium.getVariable('padding'),
},
}),
{ name: 'ExtendedThemeComponent' }
);
`TypeScript Support
This package is written in TypeScript and provides full type definitions. All APIs are fully typed with intelligent inference:
$3
Type inference works automatically, and using
createStyles provides even better type safety:`tsx
// Basic type inference - works, but ClassKey is inferred as string
const useStyles = makeStyles({
root: { color: 'red' },
button: { padding: '10px' },
}, { name: 'Component' });
const classes = useStyles(); // classes: Record// With createStyles - ClassKey is inferred as literal union type
const useStyles = makeStyles((theme) =>
createStyles({
root: {
color: theme.colors.text.static_icons__default.getVariable('color'),
},
button: {
padding: theme.spacing.comfortable.medium.getVariable('padding'),
},
}),
{ name: 'Component' }
);
const classes = useStyles(); // classes: Record<'root' | 'button', string>
// TypeScript will error if you try to access classes.foo - it knows the exact keys!
`$3
The
FusionTheme type can be extended with custom properties:`tsx
import type { FusionTheme, StyleDefinition } from '@equinor/fusion-react-styles';interface MyAppTheme extends StyleDefinition {
customProperty: string;
}
const useStyles = makeStyles((theme: FusionTheme) => ({
root: {
color: theme.colors.text.static_icons__default.getVariable('color'),
custom: theme.customProperty, // Fully typed!
},
}), { name: 'Component' });
`$3
When using
createStyles, the return type is Record where ClassKey is a literal union of your style keys. This provides:- Compile-time safety: TypeScript errors when accessing undefined class keys
- Better autocompletion: IDEs show exactly which class names are available
- Refactoring support: Rename class keys and TypeScript will catch all usages
Migration from Material-UI v4
This package provides a similar API to
@material-ui/styles v4 but is built for React 19 compatibility. The main differences:1. No Material-UI dependency - Uses JSS directly
2. React 19 compatible - No compatibility issues
3. Improved TypeScript types - Better type inference
4. Scoped styles - Enhanced
StylesProvider with seed-based isolation
5. Required name option - Always provide a name in makeStyles options for performanceMost code should work with minimal changes:
`tsx
// Before (Material-UI v4)
import { makeStyles } from '@material-ui/styles';// After (this package)
import { makeStyles } from '@equinor/fusion-react-styles';
// Add name option to makeStyles
const useStyles = makeStyles({ ... }, { name: 'ComponentName' });
`React 19 Compatibility
This package is fully compatible with React 19 and has been tested extensively. The implementation uses React 19's new APIs and avoids deprecated patterns.
Performance Tips
1. Always provide a
name option - Without it, all instances share the same cache key, causing unnecessary re-renders and performance issues.2. Use
createStyles for better type safety - While optional, it improves TypeScript inference and provides better autocompletion.3. Memoize style objects when possible - If creating styles inside components, consider moving them outside or memoizing them.
4. Leverage style caching - The package automatically caches stylesheets based on styles, theme, and name, so identical style definitions are reused.
Contributing
Contributions are welcome! Please read our Contributing Guide for details.
License
ISC
Related Packages
@equinor/fusion-web-theme - Fusion design system theme
- @equinor/fusion-wc-theme` - Fusion theme web component