Shimmer

The Shimmer component is a loading indicator that takes the shape of the component being loaded. Instead of blocking the entire page like a traditional full-screen loader, Shimmer loaders are component-shape specific to show users previews of what’s loading on the page.

These previews improves the perceived performance of the app and prevents CLS (Content Layout Shift). The Shimmer component eliminates most of the CLS on a page, which helps improve Google Lighthouse scores.

Parameters

Name Type Description
classes Object Styles to apply to the root of the Shimmer. Available classes are root and root_[TYPE].
borderRadius string | number Border radius for the shimmer.
height string | number Sets the height of the Shimmer. Numbers are in rem units. Strings are used directly (Example: ‘100px’).
width string | number Sets the width of the Shimmer. Numbers are in rem units. Strings are used directly (Example: ‘100px’).
style Object CSS styles to apply to the Shimmer.
type 'rectangle' | 'button' | 'checkbox' | 'radio' | 'textArea' | 'textInput' The base element shape to apply to the Shimmer.
children node Children to output within the Shimmer. Useful for setting image placeholders.

Properties

Name Type Description
classes Object is an object containing the class names for the Shimmer component.
classes.root string is the class for the container
classes.root_rectangle string is the class for the container of type rectangle
classes.root_button string is the class for the container of type button
classes.root_checkbox string is the class for the container of type checkbox
classes.root_radio string is the class for the container of type radio
classes.root_textArea string is the class for the container of type textArea
classes.root_textInput string is the class for the container of type textInput
borderRadius number | string is the border radius of the Shimmer
height number | string is the height of the Shimmer
width number | string is the width of the Shimmer
style Object is an object of inline styles
type string is the type of the Shimmer
children node are the children of the Shimmer

Source Code: pwa-studio/packages/venia-ui/lib/components/Shimmer/shimmer.js

Shimmer for Components

When loading data, previously we would return null (or a full-screen loader) instead of the actual component. We can now return a shimmer version of the component, which will take up the same space without relying on data.

Direct use of the Shimmer component within normal components should be avoided when possible. If the shimmer is replacing a component that is imported, a .shimmer.js file should be created for that imported component in its folder and exported in its index.js.

Example

There are 4 critical files for creating a Shimmer component:

  • Main.js - the main component that has the loading status and would usually return null for the component while loading
  • SubComponent/index.js - Previously would only export the main component. Must now export named variable for shimmer component
  • SubComponent/subComponent - Same SubComponent as usual
  • SubComponent/subComponent.shimmer.js - .shimmer.js extension is used for easily identifying that it’s a shimmer and the component it’s attached to. It can contain complex arrangement of base Shimmer elements, or include other subcomponents Shimmers.

Main.js

import React from 'react';
import SubComponent, { SubComponentShimmer } from '../path/to/SubComponent';
// ....
export default () => {
    const { data, isLoading } = fetchData();

    if (isLoading) {
        return (
          <SubComponentShimmer />
        );
    }

    if (!data) {
        return 'No data';
    }

    return (
        <SubComponent someValue={data} />
    );
};
// ....

../path/to/SubComponent/index.js

export { default } from './subComponent.js';
// Export named shimmer component
export { default as SubComponentShimmer } from './subComponent.shimmer.js';

../path/to/SubComponent/subComponent.js

import React from 'react';
import { shape, string } from 'prop-types';
import { useStyle } from '../../path/to/classify';
import defaultClasses from './subComponent.css';
const SubComponent = (props) => {
    const classes = useStyle(defaultClasses, props.classes);
    const { someValue } = props;

    return (
        <div className={classes.root}>
          <div className={classes.item}>{someValue}</div>
        </div>
    );
};
SubComponent.defaultProps = {
    classes: {}
};
SubComponent.propTypes = {
    classes: shape({
      root: string,
      item: string
    })
}
export default SubComponent;

../path/to/SubComponent/subComponent.shimmer.js

import React from 'react';
import { useStyle } from '../../path/to/classify';
import Shimmer from '../path/to/base/Shimmer';
import defaultClasses from './subComponent.css'; // Load same classes as real SubComponent
const SubComponentShimmer = (props) => {
    // Important to still merge-in prop classes for extensibility/targetability
    const classes = useStyle(defaultClasses, props.classes);

    return (
      <div className={classes.root}>
        <Shimmer className={classes.item} />
      </div>
    );
};
SubComponentShimmer.defaultProps = {
  classes: {}
};
SubComponentShimmer.propTypes = {
  classes: shape({
    root: string,
    item: string
  })
}
export default SubComponentShimmer;

Adjusting existing Shimmers

When you make layout changes to a Shimmer’s parent component, you should also adjust the Shimmer component to match. In this example, we’ll add a custom attribute shimmer to the detail section of the product page.

local-intercept.js

const { Targetables } = require('@magento/pwa-buildpack');
const targetables = Targetables.using(targets);
const productShimmerComponent = targetables.reactComponent(
    '@magento/venia-ui/lib/RootComponents/Product/product.shimmer'
);

/**
 * As a best practice, you should create a separate Shimmer file for the new attribute and import it into the
 * productShimmerComponent. But for simplicity, we'll inline the jsx as shown here.
 */
productShimmerComponent.appendJSX(
    'section className={classes.details}'
    `<div className={classes.detailsTitle}>
        <Shimmer width="100%" height={1} />
     </div>
     <Shimmer width="100%" height={1} />`
);

Accessibility

To maintain accessibility for screen readers, we can pass aria-live="polite" aria-busy="true" to the Shimmer component (or an element that wraps the Shimmer(s) in a more complex instance).

It’s important to then add aria-live="polite" aria-busy="false" to the normal component that replaces the shimmer.

Example

// ....
import Shimmer from '../path/to/base/Shimmer';
// ....
export default () => {
  // ....
  return (
    <Shimmer />
  );
};