INTRO
I've been relying on Material UI Box components quite a lot, because doing so allows use of theme-aware sx property and common attributes such as display or gap. It makes code more consistent with other uses of MUI components.
The Box output is lightweight (it's just a div) but I was wondering how making plenty of these can impact performance, so I've build a test app to (ab)use the Box...
TL;DR: It's quite unlikely that Box vs div performance might become an issue in real application.
MUI v6
Just when I was writing this text, MUI team has released the v6.0.0 of Material UI. The release announcement blog post mentions runtime performance improvements and experimental availability of Pigment CSS (zero-runtime CSS-in-JS solution that will eventually replace use of Emotion and allow sx property on plain divs).
I'm keeping this post focused on v5 (to be precise: v5.16.7 which was released less than 3 weeks ago). There are many projects that use v5 and will stay with it for a while. Plus it might be useful to compare the two major versions in the future...
STRESS TEST APP
I've made a small application (vite: 5.4.1, react: 18.3.1, @mui/material: 5.16.7, typescript: 5.5.3) to generate lots of squares with random colors (either by rendering plain divs elements or by using the MUI Box).
Live demo is here: https://morzel85.github.io/blog-post-mui-5-box-performance
The code is here: https://github.com/morzel85/blog-post-mui-5-box-performance
When the MAKE button is pressed, the app generates chosen amount of items. Use the CLEAR button to remove all items. Toggling between Plain div and MUI box options rerenders all the items. Divs are green, Boxes are purple. Simple.
Here's how the divs are created:
import { memo } from 'react';
export const PlainDivs = memo(({ amount }: { amount: number }) =>
Array.from({ length: amount }, (_, i) => (
<div
key={i}
style={{
background: 'darkgreen',
opacity: Math.random(),
margin: '1px',
width: '15px',
height: '15px'
}}
/>
))
);
Yeah, inline styles are used even for static properties. These could be extracted out to single class but this is to make it closer to the Box/sx version.
This is how Box items are done:
import { memo } from 'react';
import { Box } from '@mui/material';
export const MuiBoxes = memo(({ amount }: { amount: number }) =>
Array.from({ length: amount }, (_, i) => (
<Box
key={i}
sx={{
background: 'purple',
opacity: Math.random(),
margin: '1px',
width: '15px',
height: '15px'
}}
/>
))
);
Notice that the random opacity rule is quite unfavorable for MUI/Emotion as it will generate a lot of different CSS classes that must be injected to the page at runtime! The generated CSS rule might look like this:
.css-s5s1br {
background: purple;
opacity: 0.846957;
margin: 1px;
width: 15px;
height: 15px;
}
DESKTOP RESULTS
Here's a couple of results from my 7 years old PC with Intel Core i7-7700K and 16 GB RAM (MSI GTX 1080 Ti still going strong!) with Chrome 128 on Ubuntu 20.04.
- For the default 500 items generating items feels practically instant for both Plain div and MUI Box. Same for 1K.
- Let's go for 5K: divs are about 0.3s, Boxes about 0.4s.
- How about 15K? Ok, now there's about a 1.2s of lag for div version and maybe 1.4s for Box.
- Well... 50K? 5,5s for divs and about 6,5s for Boxes.
Quick sanity check: document.querySelectorAll('*').length -> 50056. It really created all those elements. Nice job React, nice job MUI! Aren't modern browsers a marvel of engineering? I remember the times when we had to worry about not putting too many JS validations on a form...
The above highly scientific results we collected by my eyeballs an stopwatch (time between pressing the MAKE button and page becoming responsive).
If you want something more precise here's performance trace for a 10K items:
Speed-wise there's not that much difference between Plain divs and Boxes version, although you can see that Box versions uses about 3.5x more memory. Sounds like a lot but the 10K of Boxes (with unnaturally large amount of unique classes) took about 30 MB.
Watch out for tests with too many items (especially if you open Elements tab), DevTools might choke a bit...
MOBILE RESULTS
Ok, how about a phone? This is how my 3+ years old, non-flagship, Samsung Galaxy A52 performs (Chrome 127 on Android 14):
- 500 and 1K items: instant for both divs and Boxes.
- 5K: about 0.5s for divs and 0.9s for Boxes.
- 15K: about 1.7s for divs and 3.3 for Boxes.
- and finally the absurd 50K: about 15s for divs and 22s for Boxes (hope you never have to render this many elements on a desktop, let alone mobile)...
Speaking of DOM size, Lighthouse provides warnings and errors for excessive DOM size (as of this writing the thresholds are about 800 and 1400 elements respectively). It also reports DOM depth, it's a performance factor too (which my little app doesn't check, but the Box doesn't increase it). The largest sizes I've seen in practice was about 25K elements. When stuff like this happens is usually caused by a data grid with complex cell renderers and lack of virtualization (columns virtualization is important too).
BONUS INFO
When application is running in release mode (result of: npm run build), MUI/Emotion doesn't create individual style elements for each class.
When you click on <style> to see where the CSS rule is defined:
you will land on style element that appears empty:
This is a bit confusing, where are the classes? Emotion uses insertRule API which is very fast but the disadvantage is lack of DevTools support (check this GitHub issue and this answer in particular).
Update 2024-09-08:
I have a follow-up post that checks div performance without React: https://en.morzel.net/post/vanilla-div-performance