# Customize POS styles and behavior Author the InStore POS theme with palette tokens, MUI component overrides, responsive layout tokens, and POS-specific style hooks. This page explains what goes in a theme and how to shape the POS with it. Two companion pages cover the mechanics: - [Run InStore POS API requests](/instore/customization/pos-api-access.md): set the base host, project, and tenant, get a token, and run calls. - [Theme API reference](/instore/customization/pos-theme-api.md): the `GET`, `PUT`, and `PATCH` theme endpoints, with request/response examples. ## Prerequisites - A Module Federation host running the standard InStore POS setup. - [MUI v7](https://v7.mui.com/material-ui/) (`@mui/material ^7.3.7`) in the host. Payloads mirror the MUI 7 `ThemeOptions` shapes. If you have themed MUI 5 or 6 before, the `palette`, `typography`, `components`, `breakpoints`, `spacing`, and `shape` fields are structurally compatible. - To deliver a theme through the API, an InStore access token and an HTTP client. To set up authentication and send requests, see [Run InStore POS API requests](/instore/customization/pos-api-access.md). ## MUI theming concepts The InStore POS theme uses the public MUI theme shape plus a small number of InStore-specific fields. These MUI concepts appear throughout the payload: - Theme: the object holding styling decisions such as palette, typography, spacing, breakpoints, shape, and per-component overrides. - Palette: named color tokens such as `primary.main`, `secondary.main`, `text.primary`, and `background.default`. - Token: a named value in the theme that other rules reference instead of hard-coding a literal value. - Slot: a named structural part inside a component, such as `root`, `label`, `startIcon`, or `paper`. - `styleOverrides`: per-slot style rules for a component. - `defaultProps`: default React props applied to each instance of a component. - `variants`: conditional style blocks that apply when a component matches a prop or `className`. - State class: a class MUI adds for interaction state, such as `Mui-disabled`, `Mui-focused`, or `Mui-selected`. For general MUI details, see the [MUI theming guide](https://v7.mui.com/material-ui/customization/theming/) and the [MUI component reference](https://v7.mui.com/material-ui/all-components/). ### InStore-specific theme fields The terms below are specific to how the InStore POS and its Theming API work. - Bundled default theme: the theme that the InStore POS ships with and uses when no API theme is set. It has the same shape as the API payload, so everything on this page applies to it; your tenant's look is driven by the theme you store through the Theming API. - `customTokens`: POS-specific responsive layout flags that aren't part of the MUI stock theme (header/footer visibility, sidebar structure). Documented in [Responsive layout tokens](/instore/customization/pos-theming.md#responsive-layout-tokens). - `__fn:ownerState`: a JSON discriminator used inside `styleOverrides` to express the MUI callback-form conditional styling without sending a JavaScript function. The API accepts JSON payloads only, and accepting arbitrary function strings would be an unacceptable code-injection vector, so the MUI `(ownerState) => ({...})` form is replaced by a strictly validated `{ "__fn": "ownerState", "cases": { ... } }` shape. Only `"ownerState"` is allowed; any other discriminator is rejected with a 400. To define conditional component styles with the supported JSON shape, see [The ownerState escape hatch](/instore/customization/pos-theming.md#the-ownerstate-escape-hatch). ### Slots, styleOverrides, variants, and defaultProps These are the main building blocks for overriding a component: - `defaultProps` set baseline props for each instance of a component. - `styleOverrides` target a component slot, such as `components.MuiButton.styleOverrides.root`. - `variants` apply only when props match. The POS uses class-name variants such as `{ "props": { "className": "instore-primary-action" }, "style": { ... } }` to target a specific POS surface. Use `styleOverrides` when a change should apply to every instance of a component. Use a `variants` entry or nested `instore-*` selector when a change should apply to one POS surface. ### How overrides compose Theme rules compose from general to specific: 1. Palette, typography, spacing, shape, and breakpoints establish the token values. 2. `defaultProps` set baseline component behavior. 3. `styleOverrides` style each component slot. 4. `variants` apply when a prop or class-name matcher matches. 5. State classes such as `Mui-disabled` and `Mui-selected` apply interaction-state styles. When two rules style the same property, CSS specificity decides which rule has precedence. A class-name variant or state-specific selector usually has precedence over a bare slot rule. ## How themes work The InStore POS ships with a bundled default theme that the runtime uses by default. The InStore Theming API stores a tenant-scoped theme that overlays the bundled default. At runtime, the POS deep-merges the API theme on top of the bundled default. API values win at the leaf level, except component overrides under `components.MuiX`, which are replaced atomically. For more information, see [Leaf merge versus atomic component replacement](/instore/customization/pos-theming.md#leaf-merge-versus-atomic-component-replacement). Conceptually, treat the bundled default theme as the baseline your tenant theme overlays: the theme you store through the Theming API is what's applied, and any value it doesn't set falls back to the bundled default. ```mermaid flowchart LR subgraph api[InStore Theming API] Store[(Tenant theme)] Endpoint[GET, PUT, and PATCH /theme] Store <--> Endpoint end subgraph host[InStore POS host] Fetch[Theme fetch] Bundled[Bundled default theme] Merge[deep-merge] MUI[MUI useTheme] Fetch --> Merge Bundled --> Merge Merge --> MUI end subgraph posmods[InStore POS modules] Cart[Cart] Catalog[Catalog] Transactions[Transactions] Receipt[Receipt reprint] end Endpoint -->|JSON| Fetch MUI -.->|inherits| Cart MUI -.->|inherits| Catalog MUI -.->|inherits| Transactions MUI -.->|inherits| Receipt classDef apicls fill:#FFE7A8,stroke:#FFC738,stroke-width:2px,color:#191741; classDef hostcls fill:#C2C2FF,stroke:#6359FF,stroke-width:2px,color:#191741; classDef posmod fill:#9FF7EE,stroke:#08A88A,stroke-width:2px,color:#003037; class Store,Endpoint apicls; class Fetch,Bundled,Merge,MUI hostcls; class Cart,Catalog,Transactions,Receipt posmod; style api fill:#FFF7E6,stroke:#FFC738,stroke-width:2px,color:#191741; style host fill:#EEECFF,stroke:#6359FF,stroke-width:2px,color:#191741; style posmods fill:#E6FBF7,stroke:#08A88A,stroke-width:2px,color:#003037; ``` The InStore POS modules inherit the merged theme through the MUI `useTheme`. The `/theme` API applies to non-core POS functionality such as Cart, Catalog, Transactions, and Receipt reprint. To theme core functionality such as payment, refund, cash management, and device management in an older modular-store host, see [Override product module styling](/instore/customization/product-module-styling-overrides.md). ![InStore POS with the bundled default theme](https://docs.commercetools.com/instore/images/pos-theming-default.png) ![InStore POS after applying a branded theme](https://docs.commercetools.com/instore/images/pos-theming-branded.png) The theme fetch happens at bootstrap, so a persisted change takes effect on the next load of the POS. For the per-method request flow and timing, see [How a request takes effect in the InStore POS](/instore/customization/pos-theme-api.md#how-a-request-takes-effect-in-the-instore-pos). ### Leaf merge versus atomic component replacement Your tenant theme overlays the bundled default rather than replacing it wholesale, but it does so under two different rules, and the second one surprises people: - Most fields merge at the leaf. For `palette`, `typography`, `spacing`, `shape`, and `breakpoints`, only the individual values you send change; their siblings keep the bundled default. Set `palette.primary.main`, and `palette.secondary` is left exactly as it was. - Each `components.MuiX` block is replaced as a whole. When your payload includes a component, say `components.MuiButton`, that entire block (`defaultProps`, every `styleOverrides` slot, and every `variants` entry) replaces the stored `MuiButton` definition. Components you don't mention are untouched, but the one you do mention is taken exactly as you send it, with nothing merged in from the previous block. This holds for both `PUT` and `PATCH`. Even a `PATCH`, which preserves the top-level fields you omit, replaces any `components.MuiX` block you include wholesale. ```mermaid flowchart TB subgraph leaf["Leaf merge"] L1["Send palette.primary.main"] --> L2["Only that leaf changes"] --> L3["Siblings such as palette.secondary are kept"] end subgraph atomic["Atomic replacement"] A1["Send components.MuiButton"] --> A2["The whole MuiButton block is replaced"] --> A3["Slots and variants you omit are dropped"] end L3 --> Note["Same for PUT and PATCH"] A3 --> Note classDef ok fill:#9FF7EE,stroke:#08A88A,stroke-width:2px,color:#003037; classDef warn fill:#F9C9C0,stroke:#FF8A00,stroke-width:2px,color:#191741; classDef note fill:#FFE7A8,stroke:#FFC738,stroke-width:2px,color:#191741; class L1,L2,L3 ok; class A1,A2,A3 warn; class Note note; ``` Leaf fields merge value-by-value, keeping siblings; a `components.MuiX` block is taken whole, so resend the entire block under both `PUT` and `PATCH`. What this means for you: send the complete component block, including the parts you want to keep. If you send only the slot or variant you are changing, everything you left out is dropped, not preserved. The reliable workflow is to [`GET /theme`](/instore/customization/pos-theme-api.md#get-theme), find the component block, edit it in place, and send the whole block back. For example, suppose the stored theme styles `MuiButton` like this: ```json title="Stored MuiButton block" { "components": { "MuiButton": { "defaultProps": { "disableElevation": true }, "variants": [ { "props": { "className": "instore-primary-action" }, "style": { "backgroundColor": "primary.main" } }, { "props": { "className": "instore-search-scan-btn" }, "style": { "backgroundColor": "secondary.main" } } ] } } } ``` You want to recolor only the primary action, so you `PATCH` `MuiButton` with just that one variant: ```json title="PATCH: only the primary-action variant" { "components": { "MuiButton": { "variants": [ { "props": { "className": "instore-primary-action" }, "style": { "backgroundColor": "tertiary.main" } } ] } } } ``` Because the block is replaced, not merged, the response (and the applied theme) is exactly what you sent. The `disableElevation` default prop and the `instore-search-scan-btn` variant are now gone: ```json title="Resulting MuiButton block" { "components": { "MuiButton": { "variants": [ { "props": { "className": "instore-primary-action" }, "style": { "backgroundColor": "tertiary.main" } } ] } } } ``` To keep them, include `disableElevation` and the scan-button variant in the same payload. #### Why replace instead of deep-merge? Component blocks mix nested selectors, pseudo-class rules, and an array of `variants`. Deep-merging that structure is ambiguous and fragile. For example, there is no unsurprising way to merge two `variants` arrays. A silent merge could also keep default rules you never meant to preserve. That can produce conflicting or duplicated styles. It can also create specificity battles that are hard to debug. Atomic replacement keeps each component predictable and self-contained. The block you send is exactly what applies. What you author is what renders, without leftover rules from the bundled theme. The cost is resending the full block. ## Default and POS starter themes You don't author a theme from a blank page. Two starting points are available: - Default theme: the bundled look the POS falls back to when no theme is stored. It resembles the legacy InStore look. Sending an empty body to `PUT /theme` resets the tenant to this look. - POS starter theme: an industry-typical configuration that reflects how most retailers expect a POS to look. Load it with `PUT /theme` and then adjust it with `PATCH` to match your brand. Loading a starter theme is a single `PUT` of the provided payload; from there, every change is a small `PATCH`. To review the request and empty-body behavior, see [PUT /theme](/instore/customization/pos-theme-api.md#put-theme). ## Back up your theme payloads The InStore POS stores only the currently active theme. It keeps no server-side version history or change log, and commercetools cannot recreate a configuration you overwrite or lose. Build a simple backup habit: - Save every payload. Keep your `PUT` and `PATCH` request bodies, and the `GET` responses, in your own version control or file store. These are also how you maintain alternative "modes" (seasonal, per-brand). The server keeps only one, so you track the rest. - Read before you write. Run [`GET /theme`](/instore/customization/pos-theme-api.md#get-theme) before each change and use its response as the base for your next request, so you always edit the live state. - Validate generated payloads. If you use generated or assisted theme payloads, review the JSON before applying it. Confirm that `components.MuiX` blocks are complete and that values follow the validation rules. ## The theme payload Your theme is expressed in the MUI public `ThemeOptions` shape plus a small set of InStore-specific fields, so standard MUI theming knowledge applies directly. Three concepts recur throughout: - Palette: named color tokens referenced as strings (for example, `'primary.main'`) elsewhere in the theme. - Components: per-MUI-component overrides; each accepts `defaultProps`, `styleOverrides`, and `variants`. - `customTokens`: POS-specific responsive flags for layout, not part of stock MUI. For the full list of top-level fields and their types, see the [Theme payload fields](/instore/customization/pos-theme-api.md#theme-payload-fields) table in the API reference. The rest of this page explains how to author each part. ## Customize MUI component overrides Under `components`, you key by MUI component name (`MuiButton`, `MuiAppBar`, `MuiPaper`, and so on) and provide any combination of three sub-shapes: - `defaultProps`: sets the default React props for every instance of that component. Useful for global decisions such as `disableElevation: true` on every button. - `styleOverrides`: keyed by component slot (the named structural parts MUI exposes; for example, `MuiButton` exposes `root`, `label`, `startIcon`, `endIcon`). Values are CSS-in-JS objects. Each MUI component documents its own slot list. See the component pages in the [MUI 7 docs](https://v7.mui.com/material-ui/all-components/). - `variants`: conditional style blocks that activate when a prop matcher matches. The POS uses this heavily with `className` matchers like `{ props: { className: 'instore-primary-action' }, style: { ... } }` to apply a named visual variant only when a component is tagged with that class. ```json title="components.MuiButton example" { "components": { "MuiButton": { "defaultProps": { "disableElevation": true }, "styleOverrides": { "root": { "borderRadius": "8px", "textTransform": "none", "&:hover": { "boxShadow": "none" } } }, "variants": [ { "props": { "className": "instore-primary-action" }, "style": { "backgroundColor": "primary.main", "color": "primary.contrastText", "height": 68, "fontSize": 20 } } ] } } } ``` Component override values aren't plain CSS. The InStore POS resolves them as MUI [`sx`](https://v7.mui.com/system/getting-started/the-sx-prop/). That's what lets your overrides reference palette tokens by string instead of repeating hex values. ### Reference palette tokens in component overrides Inside `styleOverrides`, `variants[].style`, and `__fn` cases, reference a palette token by string and the runtime resolves it against the active palette: - Brand colors: `'primary.main'`, `'primary.contrastText'`, `'secondary.main'`, `'tertiary.main'`. - Text and surfaces: `'text.primary'`, `'text.secondary'`, `'background.default'`, `'background.paper'`, `'divider'`, `'common.white'`, `'common.black'`. - Semantic states: `'action.hover'`, `'action.disabledBackground'`, `'action.disabled'`. MUI auto-derives `light`, `dark`, and `contrastText` from `main` for its standard colors (`primary`, `secondary`, `error`, `warning`, `info`, `success`), so you can reference `'primary.dark'` for a hover state even if your palette only sets `primary.main`. The custom `tertiary` color is not auto-derived. Define any shade you reference (`tertiary.dark`, `tertiary.contrastText`) explicitly in the palette, as in the [worked example](/instore/customization/pos-theming.md#worked-example-recolor-the-primary-action-button) below. ```json title="Hard-coded hex vs. a token reference" { "components": { "MuiAppBar": { "styleOverrides": { "root": { "backgroundColor": "primary.main", "color": "primary.contrastText" } } } } } ``` The benefit is to define once and propagate everywhere: change `palette.primary.main`, and every override that references it updates together. Re-branding becomes a one-line edit instead of a search-and-replace across scattered hex values. The bundled POS theme references tokens throughout for exactly this reason. In a JSON payload you can't send the MUI `({ theme }) => ({ ... })` callback, so the token-string form is the API-safe way to stay palette-aware. For prop-conditional styles, use [the ownerState escape hatch](/instore/customization/pos-theming.md#the-ownerstate-escape-hatch). Its case styles resolve as `sx` too, so they can reference tokens. Because override values resolve as `sx`, numeric spacing and radius are theme multiples, not pixels: `"borderRadius": 8` becomes 8 × the theme's base radius, and `"padding": 2` is 2 × the 8px spacing unit. For an exact pixel value, use a unit string: `"borderRadius": "8px"`. Any value can also be a responsive map (`{ "xs": ..., "md": ... }`). ### The ownerState escape hatch Almost everything in a theme is plain data that serializes to JSON directly. The one exception is the MUI callback form of `styleOverrides`, `({ ownerState }) => ({ ... })`, which is a JavaScript function, and JSON cannot carry functions. The `__fn` escape hatch is a small domain-specific language (DSL) that encodes that function as plain data so it survives the round trip through the API. Instead of a function body, you send a discriminator (`"__fn": "ownerState"`) and a `cases` map; when the InStore POS applies the theme, it reconstructs the equivalent callback from that data. Sending an executable function string instead would be a code-injection vector, so the schema accepts only this fixed shape: `"ownerState"` is the only discriminator, and any other value is rejected with a `400`. Each case key is a single `prop=value` test against the component's `ownerState`. The runtime applies the first matching case, falling back to the `default` key, then resolves the result as `sx`, so a case can reference palette tokens such as `'primary.main'`, just like any other override. ```tsx title="MUI callback form (in code)" { components: { MuiButton: { styleOverrides: { root: ({ ownerState, theme }) => { if (ownerState.size === 'small') return { padding: '4px 8px' }; if (ownerState.color === 'primary') return { backgroundColor: theme.palette.primary.main, color: theme.palette.primary.contrastText, }; return { padding: '6px 16px' }; }, }, }, }, } ``` ```json title="Equivalent API payload" { "components": { "MuiButton": { "styleOverrides": { "root": { "__fn": "ownerState", "cases": { "size=small": { "padding": "4px 8px" }, "color=primary": { "backgroundColor": "primary.main", "color": "primary.contrastText" }, "default": { "padding": "6px 16px" } } } } } } } ``` Each case key tests a single `ownerState` property (the part before `=`) against a value (the part after). Only `{ "__fn": "ownerState", "cases": { ... } }` is accepted. To check the schema rules that reject unsupported function payloads, see [Validation and errors](/instore/customization/pos-theme-api.md#validation-and-errors). ## Responsive layout tokens `customTokens` controls non-MUI layout decisions: header/footer visibility and sidebar structure. Every token accepts either a bare value that applies at every viewport size, or a responsive value that varies per breakpoint. ### Breakpoints in brief MUI segments the viewport into five named breakpoints (`xs`, `sm`, `md`, `lg`, `xl`) defined by minimum widths. The default thresholds are `xs: 0`, `sm: 600`, `md: 900`, `lg: 1200`, `xl: 1536` (pixels). You can override these via the top-level `breakpoints` field in your theme; `customTokens` and the MUI component styling both read from the same definitions, so a single change propagates everywhere. ### Bare value vs. responsive value - Bare value (`true`, `5`, `'inline'`): applies at every viewport. Use this when the layout decision is genuinely not viewport-dependent. Shorter, easier to read, and cheaper at runtime. - Responsive value (`{ xs?, sm?, md?, lg?, xl? }`): applies a different value per breakpoint. Use this when the same screen needs different chrome on a phone, device, or POS terminal (for example, drilldown sidebars on small screens and inline sidebars on large ones). The runtime resolves from the current breakpoint downward: if the active breakpoint is `lg` and you only set `xs` and `md`, the runtime returns the `md` value. If no key matches at or below the current breakpoint, it falls back to the bundled default. ```mermaid flowchart LR Cur["Active breakpoint: lg"] --> Lg{"lg set?"} Lg -->|yes| UseLg["use lg"] Lg -->|no| Md{"md set?"} Md -->|yes| UseMd["use md"] Md -->|no| Sm{"sm set?"} Sm -->|yes| UseSm["use sm"] Sm -->|no| Xs{"xs set?"} Xs -->|yes| UseXs["use xs"] Xs -->|no| Fallback["bundled default"] classDef src fill:#C2C2FF,stroke:#6359FF,stroke-width:2px,color:#191741; classDef dec fill:#FFE7A8,stroke:#FFC738,stroke-width:2px,color:#191741; classDef out fill:#9FF7EE,stroke:#08A88A,stroke-width:2px,color:#003037; classDef fin fill:#F9C9C0,stroke:#FF8A00,stroke-width:2px,color:#191741; class Cur src; class Lg,Md,Sm,Xs dec; class UseLg,UseMd,UseSm,UseXs out; class Fallback fin; ``` The runtime walks down from the active breakpoint to the nearest value at or below it, so an active `lg` with only `xs` and `md` set resolves to `md`, and nothing at or below falls back to the bundled default. For a broader take on responsive design (including the `useMediaQuery` hook and the `sx` prop's per-breakpoint shorthand), see the [MUI 7 breakpoints reference](https://v7.mui.com/material-ui/customization/breakpoints/). ### Field reference | Field | Attributes | Type | Description | Example | | --- | --- | --- | --- | --- | | `layout` | | Object | Visibility flags for the top-level chrome. | | | | `headerVisible` | Boolean \ | ResponsiveValue | Whether the top header bar renders. | `false` or `{ "xs": false, "md": true }` | | | `mobileFooter` | Boolean \ | ResponsiveValue | Whether the mobile status or footer bar renders. | `{ "xs": true, "md": false }` | | | `desktopFooter` | Boolean \ | ResponsiveValue | Whether the desktop status bar renders. | `{ "xs": false, "md": true }` | | `sidebar` | | Object | Sidebar structure. | | | | `showLogo` | Boolean \ | ResponsiveValue | Whether the logo renders in the sidebar header. | `true` | | | `showCollapseButton` | Boolean \ | ResponsiveValue | Whether the collapse and expand toggle renders. | `{ "xs": true, "md": false }` | A bare scalar applies at every viewport: ```json { "customTokens": { "layout": { "headerVisible": false } } } ``` A responsive boolean map gives different behavior per breakpoint: ```json { "customTokens": { "sidebar": { "showCollapseButton": { "xs": true, "md": false } } } } ``` ## Apply the theme across the InStore POS The InStore POS fetches the theme from the Theming API at bootstrap and applies it automatically, so a standard integration never needs to apply the theme from code. The active theme applies to non-core POS functionality such as Cart, Catalog, Transactions, and Receipt reprint. Supplying your own theme payload at runtime from the host app is not supported. For theme switchers, live previews, or A/B testing, deliver theme changes through the [Theme API](/instore/customization/pos-theme-api.md) and reload the POS to see them. If you have integrated InStore through the older modular-store host, the integration surface is different. To theme core functionality such as payment, refund, cash management, and device management, use [Override product module styling](/instore/customization/product-module-styling-overrides.md). This page describes the theming used by the InStore POS. ## Inspect and override a specific element Use Chrome DevTools to find what to target. Each element exposes four signals you can read straight from its class list. ### Class-name conventions For how the MUI slot, state, and prop classes work in general, see [Slots, styleOverrides, variants, and defaultProps](/instore/customization/pos-theming.md#slots-styleoverrides-variants-and-defaultprops). The list below is the quick reference for what each class on a POS element tells you. - `instore-*`: POS style hooks. Target via a nested class selector inside a `styleOverrides` slot (`"&.instore-..."`) or a `variants` entry with a `className` matcher. See the [POS style reference](/instore/customization/pos-style-reference.md#component-class-reference) for the full inventory and the exact slot per hook. - `.MuiX-{slot}`: MUI structural classes. The suffix (`root`, `label`, `startIcon`, ...) is the key you use under `styleOverrides`. Slot inventories live in the [MUI 7 component docs](https://v7.mui.com/material-ui/all-components/). - `.Mui-{state}`: interaction states (`Mui-disabled`, `Mui-focused`, `Mui-selected`). Nest under `&` inside `styleOverrides`: `"&.Mui-disabled": { "opacity": 0.5 }`. - `.MuiX-{variantOrColor}`: prop-driven classes (`MuiButton-contained`, `MuiButton-colorPrimary`). Target with nested selectors, or use the [ownerState escape hatch](/instore/customization/pos-theming.md#the-ownerstate-escape-hatch) for complex matching. ### Inspection workflow 1. Open the POS in a desktop browser, right-click the element you want to change, and choose **Inspect**. ![Inspecting the primary action button in DevTools](https://docs.commercetools.com/instore/images/pos-theming-inspect-step1.png) 2. Read the element's class list to identify the MUI component (`.MuiButton-root` to `MuiButton`), the slot (`label`, `startIcon`, and so on, usually `root`), the variant/color props (`MuiButton-contained`), and any `instore-*` hook. ![Reading the class list in the Elements panel](https://docs.commercetools.com/instore/images/pos-theming-inspect-step2.png) When you modify a specific component, make sure your selector scope covers only elements that include an ID or class that starts with `instore-`. 3. Decide whether the rule is global or specific. Global rules go in `components.MuiX.styleOverrides`; per-element rules go in `components.MuiX.variants` with a `className` matcher (preferred when an `instore-*` hook exists). 4. Write the override and apply it with a [`PATCH /theme`](/instore/customization/pos-theme-api.md#patch-theme) request. It deep-merges your change into the stored theme, so the rest of your configuration is preserved. Avoid `PUT` for incremental edits. It replaces the whole theme and unsets every field you omit. Then reload the POS. 5. Reload the page and re-inspect. The Styles panel shows whether your rule took effect or was overridden by a more specific selector. ![Verifying the new style applied in the Styles panel](https://docs.commercetools.com/instore/images/pos-theming-inspect-step5.png) 6. If your rule is crossed out, raise specificity or move the rule into `variants`. ### Worked example: recolor the primary action button DevTools shows the button has classes `MuiButton-root MuiButton-contained MuiButton-containedPrimary instore-primary-action`. You want the checkout call-to-action button green, not every contained button. Define the green once as a palette token (`tertiary`) and reference it from the `instore-primary-action` variant; the `Mui-disabled` state then draws from the MUI semantic `action` tokens rather than another hard-coded gray. ```json title="Re-color the primary action button" { "palette": { "tertiary": { "main": "#708238", "dark": "#5D6D2F", "contrastText": "#ffffff" } }, "components": { "MuiButton": { "styleOverrides": { "root": { "&.MuiButton-containedPrimary.Mui-disabled, &.MuiButton-primary.Mui-disabled": { "backgroundColor": "primary.main", "color": "primary.contrastText", "opacity": 0.5 }, "&.MuiButton-containedSecondary.Mui-disabled, &.MuiButton-secondary.Mui-disabled": { "backgroundColor": "secondary.main", "color": "secondary.contrastText", "opacity": 0.5 }, "&.MuiButton-contained.colorTertiary": { "backgroundColor": "tertiary.main", "color": "tertiary.contrastText" }, "&.MuiButton-contained.colorTertiary.Mui-disabled, &.colorTertiary.Mui-disabled": { "backgroundColor": "tertiary.main", "color": "tertiary.contrastText", "opacity": 0.5 }, "&.Mui-focusVisible": { "outline": "2px solid #90caf9", "outlineOffset": "2px" } } }, "variants": [ { "props": { "className": "instore-primary-action" }, "style": { "backgroundColor": "tertiary.main", "color": "tertiary.contrastText", "height": "68px", "borderRadius": "10px", "fontSize": "20px", "lineHeight": 1, "paddingLeft": "42px", "paddingRight": "42px", "paddingTop": "13px", "paddingBottom": "13px", "&:hover": { "backgroundColor": "tertiary.dark" }, "&:active": { "backgroundColor": "#4A571F" }, "&.Mui-focusVisible": { "outline": "2px solid #90caf9", "outlineOffset": "2px" }, "&.instore-primary-action.Mui-disabled, &.instore-primary-action:disabled": { "backgroundColor": "#D3D1CA", "color": "#444444", "opacity": 1 } } } ] } } } ``` Send this with a [`PATCH /theme`](/instore/customization/pos-theme-api.md#patch-theme) request, not `PUT`. The payload above is a partial theme (only `palette.primary` and `components.MuiButton`), so `PATCH` deep-merges it onto your stored theme and leaves everything else intact, whereas `PUT` would unset every field you did not send. The example sends the whole `MuiButton` block on purpose. `PATCH` replaces a `components.MuiX` block atomically (it is not deep-merged), so any slot you omit here would be dropped. Then reload and re-inspect. The Styles panel now shows the new `backgroundColor`. Because the variant references `tertiary.*`, you can re-tune the green later by editing `palette.tertiary` alone. If the rule does not take effect, check that no more-specific rule (for example, a global `.MuiButton-containedPrimary` selector elsewhere) is winning the cascade. ![Result: green primary action button](https://docs.commercetools.com/instore/images/pos-theming-inspect-result.png) When in doubt, prefer `variants` with a class matcher over deeply nested `&.MuiButton-...` selectors. Variants are clearer to read and survive future MUI minor-version changes that may rename internal classes. ## Override hierarchy in practice Theme rules compose in layers, from global to specific. See [How overrides compose](/instore/customization/pos-theming.md#how-overrides-compose) for the full model. The example below shows how those layers stack up on a real InStore POS element: the primary action button. It is styled by the bundled default theme's palette, by `components.MuiButton.defaultProps` and `styleOverrides` (which apply to every button), and finally by a class-targeted `variants` entry that activates only for buttons tagged `instore-primary-action`. ```json title="Composed override for the primary action button" { "palette": { "primary": { "main": "#15375C", "contrastText": "#ffffff" } }, "components": { "MuiButton": { "defaultProps": { "disableElevation": true }, "styleOverrides": { "root": { "borderRadius": "8px", "textTransform": "none" } }, "variants": [ { "props": { "className": "instore-primary-action" }, "style": { "backgroundColor": "primary.main", "color": "primary.contrastText", "height": 68, "fontSize": 20, "&:hover": { "backgroundColor": "primary.dark" }, "&.Mui-disabled": { "backgroundColor": "action.disabledBackground", "color": "action.disabled" } } } ] } } } ``` Every `