Understanding the composeClasses Utility in Material-UI: A Complete Guide
The composeClasses utility is a core helper function in Material-UI that generates final CSS class strings for component slots by merging generated utility classes with user-provided overrides.
The composeClasses utility is a fundamental building block within the MUI ecosystem, located in the @mui/base package. It standardizes how CSS class names are constructed and applied to different parts—or "slots"—of a component. Understanding this utility reveals how Material-UI maintains consistent naming conventions while allowing flexible customization through the classes prop.
What Is the composeClasses Utility?
composeClasses is a generic TypeScript function that transforms semantic slot definitions into ready-to-use CSS class strings. In Material-UI, a slot represents a distinct part of a component that can receive styling, such as the root element, an icon, or a label.
The utility takes three arguments to perform this transformation:
slots: An object mapping each slot name to an array of class keys (e.g.,"root","disabled","sizeSmall"). These keys represent the semantic states and variants of the component.getUtilityClass: A function that converts class keys into the actual utility class strings following MUI's naming convention (e.g.,getUtilityClass('root')returns'MuiButton-root').classes(optional): The user-providedclassesprop that allows consumers to override or extend the generated utility classes for any slot.
How composeClasses Works Under the Hood
The implementation in packages/mui-base/src/composeClasses/composeClasses.ts iterates over each defined slot, generates the utility classes for that slot's keys, and merges them with any user-provided overrides.
Here is the simplified core logic:
function composeClasses<Slot extends string>(
slots: Record<Slot, readonly string[]>,
getUtilityClass: (slot: string) => string,
classes: Record<string, string> = {}
) {
const output: Partial<Record<Slot, string>> = {};
Object.keys(slots).forEach((slot) => {
const slotKeys = slots[slot as Slot];
const utilityClasses = slotKeys.map(getUtilityClass);
const userClass = classes[slot];
output[slot as Slot] = [userClass, ...utilityClasses]
.filter(Boolean)
.join(' ');
});
return output as Record<Slot, string>;
}
The function places the user-provided class first in the array, followed by the generated utility classes. The filter(Boolean) step removes any undefined values that occur when a slot has no user override. This ensures that the final string contains only valid class names separated by spaces.
Practical Examples of Using composeClasses
Basic Component Implementation
Component authors use composeClasses within a useUtilityClasses hook to generate the class map for their component slots. Here is how a custom button component implements this pattern:
// src/components/MyButton/MyButton.tsx
import { composeClasses } from '@mui/base';
import { getMyButtonUtilityClass } from './myButtonClasses';
interface MyButtonProps {
classes?: Partial<{
root: string;
label: string;
disabled: string;
}>;
disabled?: boolean;
size?: 'small' | 'medium' | 'large';
}
const useUtilityClasses = (ownerState: MyButtonProps) => {
const { disabled, size } = ownerState;
const slots = {
root: ['root', disabled && 'disabled', size && `size${capitalize(size)}`],
label: ['label'],
};
return composeClasses(
slots,
getMyButtonUtilityClass,
ownerState.classes,
);
};
export const MyButton = (props: MyButtonProps) => {
const { classes: userClasses = {}, ...other } = props;
const classes = useUtilityClasses({ ...props, classes: userClasses });
return (
<button className={classes.root} {...other}>
<span className={classes.label}>Click me</span>
</button>
);
};
Overriding Classes from the Consumer Side
Consumers can override specific slots by passing a classes prop. The composeClasses utility merges these custom classes with the generated utility classes:
<MyButton
size="small"
classes={{ root: 'myCustomRoot' }}
>
Small button
</MyButton>
The rendered output combines the user class with the generated utility classes:
<button class="myCustomRoot MuiMyButton-root MuiMyButton-sizeSmall">…</button>
Integration with Styled Components
When building custom styled components that need to maintain MUI's class naming convention, composeClasses ensures consistency:
import { styled } from '@mui/material/styles';
import { composeClasses } from '@mui/base';
import { getButtonUtilityClass } from '@mui/material/Button';
const ButtonRoot = styled('button')(({ theme }) => ({
/* default style rules */
}));
export const StyledButton = (props) => {
const { classes = {}, variant = 'contained', ...rest } = props;
const slots = {
root: ['root', `variant${capitalize(variant)}`],
};
const composed = composeClasses(slots, getButtonUtilityClass, classes);
return <ButtonRoot className={composed.root} {...rest} />;
};
Key Source Files in the Material-UI Repository
Understanding the composeClasses utility requires familiarity with these specific files in the mui/material-ui repository:
packages/mui-base/src/composeClasses/composeClasses.ts– Contains the core implementation of thecomposeClassesgeneric function.packages/mui-base/src/generateUtilityClass/generateUtilityClass.ts– Provides thegenerateUtilityClassfactory that creates thegetUtilityClassfunctions used bycomposeClasses.packages/mui-material/src/Button/Button.js– Demonstrates real-world usage ofcomposeClassesthrough theuseUtilityClasseshook pattern.packages/mui-material/src/Button/buttonClasses.js– Defines the slot keys and utility class generator for the Button component.packages/mui-material/src/utils/useUtilityClasses.js– A wrapper utility that standardizes thecomposeClassescall pattern across MUI components.
Summary
composeClassesis the central utility in Material-UI that transforms semantic slot definitions into final CSS class strings for component parts.- It accepts three parameters: a slots object mapping slot names to class keys, a getUtilityClass function for generating MUI-style class names, and an optional classes prop for consumer overrides.
- The utility merges user-provided classes with generated utility classes, placing custom classes first to allow proper CSS specificity management.
- Located in
packages/mui-base/src/composeClasses/composeClasses.ts, this function ensures consistentMui*naming conventions across all Material-UI components while supporting deep customization through theclassesprop.
Frequently Asked Questions
How does composeClasses differ from the classes prop in Material-UI?
The classes prop is the public API that consumers use to inject custom class names into specific component slots. composeClasses is the internal utility that processes this prop. It takes the user-provided classes object, combines it with the automatically generated utility classes (like MuiButton-root), and returns the final merged class strings that get applied to the DOM elements.
What is the relationship between composeClasses and generateUtilityClass?
generateUtilityClass is a factory function that creates the getUtilityClass function required by composeClasses. While generateUtilityClass produces the naming convention logic (transforming a slot key like "root" into "MuiComponent-root"), composeClasses orchestrates the application of these names across multiple slots and handles the merging with user overrides. You can find generateUtilityClass in packages/mui-base/src/generateUtilityClass/generateUtilityClass.ts.
Does using composeClasses impact runtime performance?
The performance impact is minimal. composeClasses performs simple array mapping and string joining operations that execute quickly during component render cycles. Material-UI typically calls this utility within a useUtilityClasses hook, ensuring that class generation logic is centralized and memoized where appropriate. The deterministic nature of the class generation also helps with CSS caching and server-side rendering consistency.
When should custom component authors use composeClasses?
Component authors should use composeClasses whenever building reusable components that follow Material-UI's styling architecture and need to support the classes prop for customization. It is essential when you want to maintain the MuiComponent-slot naming convention, support style overrides through the theme, and allow consumers to inject custom class names into specific slots. This utility ensures your custom components behave consistently with official MUI components regarding class name generation and customization patterns.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →