Frontend developer focused on inclusive design

Next.js & Fluent UI: Styling

Even though components from the Fluent UI library come with pre-defined styles, there are cases when it is necessary to add additional styles or customize the existing ones.

These are notes on how to style React-based application and its components using Next.js and Fluent UI React library.

Make styles

Fluent UI React library provides makeStyles, which is a utility function to define styles in components. The function originates from Griffel, CSS-in-JS implementation that generates atomic CSS classes.

Usage

In Fluent UI, makeStyles takes an object with unique keys and style values, returning a React hook for use inside a component.

To prevent unnecessary re-creation of styles during each render, define makeStyles outside the component. It not only ensures improved performance but also maintains separation between styling logic and component logic.

import { makeStyles, shorthands, tokens } from "@fluentui/react-components";

// Define the useStyles hook outside of the component
const useStyles = makeStyles({
  button: {
    backgroundColor: tokens.colorBrandBackground,
    ...shorthands.borderRadius("5px"),
  }
});

const CustomButton = ({ children }) => {
  // Use the useStyles hook inside the component
  const styles = useStyles();

  return <button className={styles.button}>{children}</button>;
};

export default CustomButton;

In the provided example, shorthands and tokens are also imported from @fluentui/react-components and used within the makeStyles styling definition.

Fluent UI design tokens

In Fluent UI React library, tokens are predefined values that represent the design system's core styles, such as colors, typography, and spacing.

Using tokens ensures a consistent look and feel throughout the application and makes it easier to apply global design changes.

In the example above, tokens.colorBrandBackground is used to set the background color of the button. This ensures that the button's background color aligns with the design system's color scheme.

List of provided tokens:

CSS shorthands

Fluent UI's shorthands is a set of functions designed to mimic CSS shorthand properties. As the library does not natively support CSS shorthand properties, using this collection of shorthand functions can help avoid issues.

const useStyles = makeStyles({
  button: {
    // This is not supported, and styles will not be inserted to DOM.
    borderRadius: "5px",
    // Use shorthand functions instead to avoid issues.
    ...shorthands.borderRadius("5px"),
  }
});

As previously mentioned, Fluent UI utilizes Griffel for CSS-in-JS functionality. Consequently, the documentation for shorthands functions is found on the Griffel documentation page.

Media queries

To apply media queries with makeStyles, use nested objects within the style definition. Media query keys should follow standard CSS syntax, starting with @media and specifying the desired conditions, such as min-width or max-width.

Inside the media query object, define the styles for those conditions.

import { makeStyles } from "@fluentui/react-components";

const useStyles = makeStyles({
  container: {
    display: "flex",
    flexDirection: "column",
    "@media (min-width: 768px)": {
      flexDirection: "row",
    },
  },
});

const ResponsiveContainer = ({ children }) => {
  const styles = useStyles();

  return <div className={styles.container}>{children}</div>;
};

export default ResponsiveContainer;

In this example, the container style definition includes a media query for screen widths larger than 768px. The flexDirection is set to 'column' by default, but when the screen width exceeds 768px, the flexDirection changes to 'row'.

This makes the ResponsiveContainer component adaptable to different screen sizes by adjusting its layout based on the specified media query.

Nested selectors

Using nested selectors in makeStyles should be done with caution, as they can cause "CSS rule explosion" and negatively impact performance.

Avoid using complex or deeply nested selectors, as they can create unique, non-reusable CSS rules, increase the bundle size, and make it harder to override styles. Instead, keep selectors simple and apply classes directly to the target element when possible.

Additionally, it's recommended to use JavaScript state rather than input pseudo-classes, as it produces fewer classes on an element and simplifies selectors for overrides. Input pseudo-classes are also limited to input elements, which can be a constraint in some cases.

Merge classes

Fluent UI provides mergeClasses, which is a utility function for managing CSS classes in React-based application.

This function combines multiple class names into a single class. Its helps in extending or overriding existing styles, making it easier to customize components while maintaining their original design.

Usage

Import the function from @fluentui/react-components:

import { mergeClasses } from "@fluentui/react-components";

mergeClasses accepts multiple class arguments, in the form of strings or objects.

function CustomComponent() {
  const styles = useStyles();

  return (
    <div className={mergeClasses(styles.base, styles.primary)}>
      Content
    </div>
  );
}

Note, do not concatenate classnames, use mergeClasses() instead.

// Incorrect usage
<div className={styles.base + ' ' + styles.primary} />

// Correct usage
<div className={mergeClasses(styles.base, styles.primary)} />

Concatenation is not recommended because it may produce wrong results.

Arguments order

In contrast to native CSS, the mergeClasses() function's output depends on the order of the classes provided, which allows for better control over the priority of style overrides.

import { mergeClasses, makeStyles } from "@fluentui/react-components";

const useStyles = makeStyles({
	base: {
		fontSize: "18px"
	},
	additional: {
		fontSize: "1.125rem"
	}
});

function Component() {
	const styles = useStyles();

	const pxSize = mergeClasses(styles.additional, styles.base);
	// Result: { fontSize: "18px" }
	const remSize = mergeClasses(styles.base, styles.additional);
	// Result: { fontSize: "1.125rem" }
}

Arguments order matters. It gives control over the precedence of styles when combining multiple classes. When classes are passed in a specific order, the styles from later classes can override the styles from earlier classes.

This is useful when customizing or extending components with additional styles, as it allows to modify or prioritize specific styles without changing the original class definitions.