Variant Matrix
Variant Matrix lets you pick one product and manage every Size × Colour combination as cells in a single grid — entering price and stock per cell, adding or removing rows/columns, and saving everything in one bulk request instead of creating each variant one at a time.
Overview
The grid is two-dimensional — rows are Size, columns are Colour. It was originally labelled "3D Variant Matrix" in the sidebar and page header, which implied a third axis (e.g. Fit, Pattern, Fabric) that never existed in the component; both labels have since been corrected to "Variant Matrix" to match what the screen actually does.
Searching for a product (by name or SKU, debounced 350ms) and selecting it loads its existing variants via GET /products/:id/variants; each variant's attributes JSON is parsed for a size and colour key to place it into the correct grid cell.
If the product has no existing variants, the grid seeds itself with a starter set (sizes S/M/L/XL, colours Black/White) so you are not starting from a completely empty grid.
Each cell independently tracks Price and Stock; a "Base Price" field with an "Apply to all" button lets you set every cell's price at once before fine-tuning individual cells.
Adding a size or colour extends the grid with a new empty row/column; removing one deletes every cell in that row/column and queues any variant IDs in it for deletion on save rather than deleting immediately.
Saving posts the entire grid — every size × colour cell, plus any queued deletions — in one call to POST /garment/products/:productId/variant-matrix, which upserts variants (matching by variantId when present) and deletes the queued ones in a single pass, then the page reloads the product's variants to pick up newly generated variant IDs and SKUs.
Before You Start
- A product must already exist in Products before you can manage its matrix — this page only edits variants of an existing product, it cannot create the base product.
- Nothing is saved to the server until you click Save Matrix — adding sizes/colours, editing cells, and removing rows/columns are all held in local state until then.
Step-by-Step Guide
1 Load a product's variant grid
- Type at least a few characters of a product name or SKU into the search box; a dropdown of up to 10 matches appears after a short pause.
- Click a result to load it — its existing variants populate the grid, and its current price fills the Base Price field.
- If the product has no variants yet, a default S/M/L/XL × Black/White grid appears instead so you have a starting layout.
2 Set prices quickly with Base Price
- Enter a value into Base Price (e.g. 499).
- Click Apply to all — every cell's Price field is set to that value at once.
- Edit any individual cell's Price afterward to override the base price for that specific size/colour if needed (e.g. a premium colour).
3 Add or remove sizes and colours
- Under Add Size, either pick one of the default sizes (XS, S, M, L, XL, XXL) from the dropdown or type a custom size, then click the + button — a new row appears.
- Under Add Colour, do the same with the default colour list (Black, White, Navy, Red, Blue, Green, Grey) or a custom colour — a new column appears.
- Click the ✕ next to a size row or colour column header to remove it — every cell in that row/column is cleared from the grid, and any variant already saved in one of those cells is queued for deletion when you next save.
4 Fill in and save the matrix
- For each Size × Colour cell, enter a Price and Stock quantity; cells that already correspond to a saved variant show their SKU underneath.
- Click Save Matrix — this sends every cell in the current grid (blank cells are still sent, saved as price null / stock 0) plus the queued deletions in one request.
- A confirmation message reports how many variants were created, updated, and deleted; the grid reloads from the server afterward so newly created cells show their generated SKU.
P{productId}-{SIZ}-{COL}-{random4digits} when you leave the SKU blank — you cannot set a custom SKU per cell from this grid.Every Field & Button, Explained
| Field / Button | What it does |
|---|---|
Product search box | Debounced (350ms) search over GET /products?search=&limit=10; matches by name or SKU, shows category as a hint in the dropdown. |
Base Price + Apply to all | A convenience field that overwrites every cell's Price at once; does not affect Stock. |
Add Size / Add Colour | Dropdown of common defaults plus a free-text box for a custom value; clicking + adds a new grid row/column. |
Grid header ✕ (remove size/colour) | Removes an entire row or column from the grid and queues any variant IDs it held for deletion on the next save — the deletion is not sent until Save Matrix is clicked. |
Cell — Price | Per-variant selling price; left blank falls back to null on save (no price override for that variant). |
Cell — Stock | Per-variant stock quantity; left blank is saved as 0. |
Cell SKU label | Shown under a cell only once it corresponds to an already-saved variant; auto-generated, not editable from this screen. |
Save Matrix | Sends the whole grid (all cells, in every size × colour combination currently shown) plus queued deletions to POST /garment/products/:productId/variant-matrix in one request. |
"N sizes × N colours = N variants total" footer | A live count reflecting the current grid size — a quick sanity check before saving a large matrix. |
Tips & Best Practices
- Set the Base Price and click "Apply to all" first, then only touch the handful of cells that genuinely need a different price — it is much faster than typing every cell by hand for large grids.
- Removing a size or colour does not delete anything until you click Save Matrix — if you remove a row by mistake, simply add the size/colour back before saving and its cells will be treated as new (a fresh SKU will be generated, since the original variant ID is gone from local state).
- Use consistent size/colour spelling across products (e.g. always "Navy", never "navy blue") — Size Curve Analysis and other reports group by these exact attribute values, so inconsistent naming fragments your data.
Troubleshooting & FAQ
I remember this being called "3D Variant Matrix" — did it change?
I removed a size/colour by mistake — how do I undo it?
A cell I filled in shows no SKU after saving.
My product search isn't returning the product I want.
Can I set a custom SKU per variant from this page?
P{productId}-{size}-{colour}-{random}); to set a specific SKU you would need to edit the variant elsewhere or rename it after creation if your Products page supports that.🧑💻 Developer Notes
Source component(s):
frontend-app/pos-app-garment/src/pages/garment/VariantMatrixPage.jsx
Backend endpoints used:
GET /products?search=&limit=10GET /products/:id/variantsPOST /garment/products/:productId/variant-matrix
Related tables (db-core repositories):
ProductProductVariant
Redux slices:
None — local component state only (useState/useCallback)
Feature flag key: inventory
Backend handler is bulkUpsertVariantMatrix in packages/pos-core/src/garment.js: it loads all existing variants for the product, deletes anything in deletedVariantIds, then loops the submitted cells array — updating a variant in place when variantId matches an existing one, otherwise inserting a new variant with attributes: JSON.stringify({ size, colour }) and an auto-generated SKU. Size/colour are stored purely as a JSON blob on ProductVariant.attributes rather than normalized columns, which is also how Size Curve Analysis later recovers the "size" for each SKU sold. The endpoint has no explicit uniqueness guard against creating duplicate size+colour cells beyond what the UI itself prevents (one cell per size/colour pair in the grid) — a second manual POST with an overlapping size/colour and no variantId would create a second variant rather than merge into the existing one.