Skip to content

Styling Components

Note: This guide applies to PWA 7.32 and later. Starting with 7.32, the app’s styling comes from a configurable theme with dark mode support. See Earlier PWA versions if your extension still uses glamor.

All colors, typography, border radii and shadows of the app come from one theme object. Merchants configure the theme in the Shopgate admin, and it supports dark mode.

If your extension takes its styling from the theme, it automatically follows the merchant’s settings and dark mode.

makeStyles from @shopgate/engage/styles gives you access to the theme in your styles. The generated class names are unique, so your styles never collide with the styles of other components.

import { makeStyles } from '@shopgate/engage/styles';
const useStyles = makeStyles()(theme => ({
root: {
display: 'flex',
background: theme.palette.background.surface,
color: theme.palette.text.primary,
borderRadius: theme.shape.borderRadius,
padding: theme.spacing(2),
'&:hover': { background: theme.palette.background.emphasized },
},
}));
const MyComponent = ({ className, children }) => {
const { classes, cx } = useStyles();
return <div className={cx(classes.root, className)}>{children}</div>;
};
  • Nested selectors start with &, for example '&:hover' or '& > span'.
  • Dynamic values: pass them as parameters, e.g. makeStyles()((theme, { isOpen }) => ({ … })) and then useStyles({ isOpen }).
  • Class components: use withStyles(Component, theme => ({ … })). The component receives classes as a prop.
  • Theme outside of styles: use useTheme() from @shopgate/engage/styles.
Token Use for
palette.background.default Page background
palette.background.surface Cards, panels, lists, sheets
palette.background.emphasized Highlighted or selected areas
palette.text.primary / .secondary Main text / secondary text
palette.primary.main / .contrastText Brand color / text on top of it
palette.secondary.main / .contrastText Accent color / text on top of it
palette.error / warning / success .main Status colors
palette.action.disabled Disabled text and icons
components.border.light / .medium / .dark Borders
components.separatorLine.borderColor Dividers
shape.borderRadius Border radius
shadowSizes.low / .medium / .strong Shadows
layout.safeArea.top / .bottom Safe area insets

The TypeScript type Theme from @shopgate/engage/styles lists all available tokens.

As long as you only use theme tokens, dark mode works without any extra code. For the rare exception:

// in makeStyles
...theme.applyStyles('dark', { boxShadow: 'none' }),
// in components
import { useColorScheme } from '@shopgate/engage/styles';
const { activeColorScheme } = useColorScheme(); // 'light' | 'dark'

theme.palette.primary.main is not a hex code. It is var(--sg-palette-primary-main). The browser resolves the value, so dark mode and admin changes take effect without a re-render. This has consequences:

  • No JS color math. Use the theme helpers theme.alpha(color, 0.12), theme.darken(color) and theme.contrastColor(color).
  • Use calc() for size tokens. For example `calc(${theme.components.navigator.height} + 8px)` instead of height + 8.

To style elements that your extension does not render itself, for example content injected by a third-party script, use injectGlobal from @shopgate/engage/styles. Selectors apply as written.

import { injectGlobal } from '@shopgate/engage/styles';
injectGlobal({
'#provider-chat-button': {
bottom: 'calc(var(--footer-height) + 16px)',
},
});

You can create CSS files and import them where you need them. Use the theme’s CSS variables instead of fixed colors:

.my-extension__badge {
display: flex;
padding: 10px;
background: var(--sg-palette-secondary-main);
color: var(--sg-palette-secondary-contrastText);
border-radius: var(--sg-shape-borderRadius);
}
import './style.css';
const Badge = ({ children }) => (
<div className="my-extension__badge">{children}</div>
);
export default Badge;

React also supports inline styles. Use them only for values that depend on the component state, and take the values from the theme:

import { useTheme } from '@shopgate/engage/styles';
const Message = ({ error, children }) => {
const theme = useTheme();
return (
<div style={{ color: error ? theme.palette.error.main : 'inherit' }}>
{children}
</div>
);
};
export default Message;
  • Buttons: Button, IconButton and ButtonBase from @shopgate/engage/components/v2.
    • Button supports variant="contained" | "outlined" | "text" | "link" and color="primary" | "secondary" | "cta" | ….
    • Button from @shopgate/engage/components is still the old, deprecated button.
  • Other components from @shopgate/engage/components:
    • Typography: text in the theme’s variants
    • Card: follows the merchant’s card setting
    • Paper: a surface with elevation

Merchants can restyle the app with their own CSS. Generated class names change with every build. That’s why you should also give stylable elements a fixed, readable class name, e.g. ext-my-extension__panel, and don’t rename it later.

  • Take colors only from the theme. No #fff, 'white' or #ccc.
  • Pick tokens by meaning, not by look: background.surface for content areas, text.secondary for secondary text.
  • Put text on colored areas with contrastText, so it stays readable with any brand color.
  • Don’t check the theme name (isIOSTheme(), themeName). Style through tokens instead.
  • Test in light and dark mode. In development builds, turn it on in the Development Settings with “Enable Color Scheme Selection”. The route /shopgate-theme-demo shows all colors, typography variants and buttons.

Before PWA 7.32, components were styled with glamor, and colors came from themeConfig.colors. Both still work in 7.32, but they don’t follow dark mode or the merchant’s theme settings. Don’t use them in new code, and replace them with makeStyles and theme tokens when you update an existing extension.