Every component, prop, hook and theme token in @shopdiddo/react, grouped by what it does.
πNew to the SDK? Start with the React SDK guide, which walks you from install to a working storefront.
π¦ Entry Points
| Import | Use it for |
|---|---|
@shopdiddo/react | DiddoProvider, theming, and the checkout components |
@shopdiddo/react/cart | Carts: CartProvider, add-to-cart buttons, cart lines and summaries |
@shopdiddo/react/products | Product catalogs: ProductsProvider, grids, filters and product details |
@shopdiddo/react/styles.css | The stylesheet. Import it even if you only use /cart or /products. |
Import only what you use. Your customers then download only what that part of the SDK needs.
π§© Setting up DiddoProvider
Every Diddo component and hook goes inside a DiddoProvider, which supplies the session token and theme. For a setup example, see step 2 of the React SDK guide.
Checkout, carts and catalogs each add their own provider inside it: CheckoutProvider, CartProvider and ProductsProvider. A Diddo component rendered outside the provider it needs throws an error naming the missing provider.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
token | string | β | Your customer's CST. You can pass a new value at any time. |
onAuthError | (info: AuthErrorInfo) => void | β | Called when Diddo rejects the token, for example because it has expired. |
theme | `Theme | PartialTheme` | Diddo theme |
loadingFallback | ReactNode | Nothing | Shown while the SDK starts up. |
errorFallback | ErrorFallback | ErrorPage | Shown in place of a Diddo component that can't continue. See Handling errors below. |
notifications | boolean | true | Shows Diddo's toast messages, such as validation errors. |
Session tokens
Mint the CST on your server, never in the browser. Mint it with your customer's email to fill in their details at checkout. See Customer Session Token (CST).
- Changing the token: pass the new value to
token. It takes effect straight away, with no reload. - Signing out: a token can't be cleared. When your customer signs out, remount the provider, for example by changing its
key. - Guest checkout: if your page only checks out a cart you already have the ID for, you can leave
tokenout. Diddo creates a guest session for that checkout itself. - One token for everything: every CST, including a guest one, works for product catalogs, carts and checkout.
Renewing an expired token
A CST expires after 24 hours. When Diddo rejects it, onAuthError is called: pass a new CST to token. The failed request isn't retried, so your customer may need to repeat their last action.
onAuthError receives the HTTP status and, when available, a requestId for Diddo support. It isn't called when a valid token lacks permission, since a new token wouldn't help.
Loading
The checkout, cart and catalog providers also take a loadingFallback. Use the same component for each, so your customer sees one continuous loading state.
Handling errors
If a Diddo component can't continue, for example because the SDK couldn't load or a cart couldn't be found, DiddoProvider shows errorFallback in its place. The default, ErrorPage, shows a title, a message for your customer, a support code and, when trying again might work, a retry button.
To customize it, pass your own component. It receives error and retry. Only offer retry when error.isRetryable is true. You can wrap ErrorPage rather than start from scratch:
typescriptimport { DiddoProvider, ErrorPage, type ErrorFallback } from '@shopdiddo/react'; const CheckoutError: ErrorFallback = ({ error, retry }) => ( <ErrorPage error={error} onRetry={retry} onGoBack={() => history.back()} /> ); <DiddoProvider token={cst} errorFallback={CheckoutError}>β¦</DiddoProvider>;
Every Diddo component inside the provider uses the same errorFallback. See Error Handling for error types and support codes.
π¬ Toast messages stay visible on top of the error screen, so your customer can still see why something failed.
More than one provider
Each DiddoProvider is completely independent: two on one page don't share a token, a cart, a theme or toast messages.
π·οΈ Product catalogs
From @shopdiddo/react/products. Every component goes inside a ProductsProvider, which needs a CST on DiddoProvider. For a catalog page and product page example, see step 3 of the React SDK guide.
ProductsProvider props
| Prop | Type | Default | Description |
|---|---|---|---|
query | ProductsQuery | β | The products to show when the catalog opens. See Choosing products below. |
loadingFallback | ReactNode | Nothing | Shown while the catalog starts up. |
loadCatalog | boolean | true | Set to false on a page that shows a single product, so the catalog isn't loaded for nothing. |
query is read once, when the provider mounts. After that, your customer changes it with ProductsFilterPanel, and you can change it with useProducts (see Hooks below). To start again from a new query, for example when your customer navigates to a different team, change the provider's key.
Choosing products
Every field of ProductsQuery is optional.
| Field | Type | Description |
|---|---|---|
league | string | A league slug, such as 'nba'. |
teams | string[] | Team slugs within the league. |
players | string[] | Player slugs. |
productsPer | number | How many products to show for each player. Only used with players. |
category | string[] | Product categories, such as 'Jerseys'. |
color | string[] | Colors. |
gender | string[] | Genders. |
age | string[] | Age groups. |
brand | string | A brand name. |
tags | Record\<string, string\>[] | Key-value tags, such as [{ color: 'red' }]. |
sort | ProductSortKey | 'best_selling', 'hot_new_arrivals', 'newest', 'price_asc' or 'price_desc'. |
minPrice / maxPrice | number | Price bounds in cents. |
limit | number | Products per page, from 1 to 100. Defaults to 20. |
imageWidth | number | The width, in pixels, to request product images at. |
gameId | string | Shows products for the two teams in one game. |
π¬With
gameId, onlysort,minPrice,maxPrice,limitandimageWidthapply. Other fields are ignored.
ProductGrid
ProductGrid shows one card per product. While the first page loads it shows placeholder cards. If loading fails it offers a retry, and while there are more products it shows a "Load more" button.
| Prop | Type | Default | Description |
|---|---|---|---|
onSelectProduct | (product: Product) => void | β | Called when a customer picks a product. Use it to open your product page. |
emptyMessage | string | 'No products found.' | Shown when nothing matches the query. |
renderItem | (card: ReactNode, product: Product) => ReactNode | β | Wraps each card, for example to add your own badge. Return card unchanged to leave it as it is. |
className | string | β | Added to the grid. |
Without onSelectProduct, picking a card opens the product on the same page: a ProductDetail inside the same ProductsProvider shows it. Use this for a drawer or a side panel, and close it with useProductSelection (see Hooks below).
typescript<ProductGrid renderItem={(card, product) => (product.available ? card : <SoldOutBadge>{card}</SoldOutBadge>)} />
ProductsFilterPanel
ProductsFilterPanel lets your customer sort and filter the catalog. By default it shows sort, category, color, gender and age options. Pass your own list to replace any of them, or an empty list to hide one.
| Prop | Type | Description |
|---|---|---|
sorts | SortOption[] | Sort options. Defaults to SORT_OPTIONS. |
categories | string[] | Category options. Defaults to CATEGORY_OPTIONS. |
colors | ColorOption[] | Color options. Defaults to COLOR_OPTIONS. |
genders | string[] | Gender options. Defaults to GENDER_OPTIONS. |
ages | string[] | Age options. Defaults to AGE_OPTIONS. |
leagues | LeagueOption[] | Lets your customer pick a league. Picking a different league clears their team and player choices. |
teams | TeamOption[] | Lets your customer pick teams in the chosen league. Only used with leagues. |
playerGroups | PlayerGroup[] | Lets your customer pick players, grouped by team. |
labels | Partial\<ProductsFilterPanelLabels\> | Overrides the panel's text. Defaults to DEFAULT_FILTER_PANEL_LABELS. |
className | string | Added to the panel. |
Without leagues, the league and teams in your query stay fixed. To build the leagues, teams and playerGroups options, use useLeagues and useTeamSummaries (see Hooks below). Each LeagueOption and TeamOption is a { value, label } pair, where value is the slug. To extend a default list rather than replace it, spread the exported default:
typescriptimport { ProductsFilterPanel, CATEGORY_OPTIONS } from '@shopdiddo/react/products'; <ProductsFilterPanel categories={[...CATEGORY_OPTIONS, 'Collectibles']} />;
ProductDetail
ProductDetail shows one product: its images, price and options (such as size), a quantity picker and an add-to-cart button. Choose which product it shows in one of three ways:
productId****: loads the product by its ID. Use this on a product page. If the product no longer exists, your customer sees "This product is no longer available."product****: shows a product you already have.- Neither: shows the product your customer picked in a
ProductGridin the sameProductsProvider. Until they pick one, it shows nothing.
| Prop | Type | Default | Description |
|---|---|---|---|
productId | string | β | The product to load. Ignored when you pass product. |
product | Product | β | The product to show. |
initialVariantId | string | First in stock | The variant to show first, for example from a link to one size. Your customer can still change it. |
onAddToCart | `(input: AddToCartInput) => void | Promise<void>` | β |
showAddToCart | boolean | true | Set to false to hide the quantity picker and button, for example when you show your own add-to-cart control. |
addToCartLabel | string | 'Add to cart' | The button's text. |
footer | ReactNode | β | Shown below the options, for example your own add-to-cart bar. |
className | string | β | Added to the product view. |
How the add-to-cart button behaves depends on where ProductDetail is:
- Inside a
CartProvider****: the button adds the chosen variant and quantity to that cart. This is the usual setup. - With
onAddToCart****: the button calls it instead, for apps that keep their own cart. If it returns a promise that rejects, your customer sees a message asking them to try again. - Neither: the product is shown read-only, with no quantity picker or button.
To use your own add-to-cart control instead, set showAddToCart={false} and put your control in footer or anywhere else on the page. Read the customer's choice with useProductSelection (see Hooks below).
Building blocks
These are the parts ProductGrid and ProductDetail are made from. Use them to build your own layout inside a ProductsProvider.
| Component | Shows |
|---|---|
ProductCard | One product as a card. Takes product and an optional onSelect. |
ProductImageCarousel | A swipeable gallery. Takes images and a title used to describe them. |
ProductOptionSelector | The option pickers, such as size, for the product currently shown. |
ProductPrice | A price. Pass variant for its exact price, with the original price struck through when it's on sale, or product for a range such as "From $29.99". |
Hooks
Use these inside a ProductsProvider to build your own catalog UI.
useProducts(): the catalog for the provider's current query.
| Field | Type | Description |
|---|---|---|
products | Product[] | The products loaded so far. |
status | `'loading' | 'ready' |
error | `ParsedError | null` |
isEmpty | boolean | Loaded, with nothing to show. |
isRefreshing | boolean | The query changed. The previous products stay on screen until the new ones load. |
hasMore | boolean | There are more products to load. |
isLoadingMore | boolean | The next page is loading. |
loadMore | () => void | Loads the next page. |
retry | () => void | Tries again after an error. |
query | ProductsQuery | The current query. |
setQuery | (changes: Partial\<ProductsQuery\>) => void | Changes some fields of the query and reloads from the first page. |
resetFilters | () => void | Clears your customer's filters and sort. The league, teams and players stay. |
To show your own filter count or "Clear filters" button, use activeFilterCount, clearedFilters and shownFilterKeys. Pass shownFilterKeys the same leagues and playerGroups you pass to ProductsFilterPanel, so your count matches the panel's:
typescriptimport { useProducts, shownFilterKeys, activeFilterCount, clearedFilters } from '@shopdiddo/react/products'; function ClearFiltersButton({ leagues }: { leagues: LeagueOption[] }) { const { query, setQuery } = useProducts(); const keys = shownFilterKeys({ leagues }); const count = activeFilterCount(query, keys); if (count === 0) return null; return <button onClick={() => setQuery(clearedFilters(keys))}>Clear filters ({count})</button>; }
useProductSelection(): the product your customer is looking at, and their choices.
| Field | Type | Description |
|---|---|---|
product | `Product | null` |
variant | `ProductVariant | null` |
optionIndices | number[] | The chosen value for each of product.optionNames, or -1 where nothing is chosen yet. |
quantity | number | The chosen quantity. |
selectProduct | `(product: Product | null) => void` |
selectOption | (optionIndex: number, valueIndex: number) => void | Chooses a value for one option. |
selectVariant | (variantId: string) => void | Chooses a variant by its ID. |
setQuantity | (quantity: number) => void | Sets the quantity. It's kept between 1 and the most your customer can buy. |
typescriptimport { ProductGrid, ProductDetail, useProductSelection } from '@shopdiddo/react/products'; function CatalogWithDrawer() { const { product, selectProduct } = useProductSelection(); return ( <> <ProductGrid /> <Drawer open={product !== null} onClose={() => selectProduct(null)}> <ProductDetail /> </Drawer> </> ); }
useLeagues() and useTeamSummaries(league, teamSlugs): the leagues and teams your customers can filter by.
useLeagues()returns{ leagues, status, error }. Each league has aslug, anameand itsteams.useTeamSummaries(league, teamSlugs)returns{ teams, isPending }. Each team includes its topplayers, the ones with the most products for sale.
Use them to build ProductsFilterPanel's options:
typescriptimport { ProductsFilterPanel, useLeagues, useProducts, useTeamSummaries } from '@shopdiddo/react/products'; function CatalogFilters() { const { query } = useProducts(); const { leagues } = useLeagues(); const { teams } = useTeamSummaries(query.league, query.teams ?? []); const league = leagues.find((option) => option.slug === query.league); return ( <ProductsFilterPanel leagues={leagues.map((option) => ({ value: option.slug, label: option.name }))} teams={league?.teams.map((team) => ({ value: team.slug, label: team.name })) ?? []} playerGroups={teams.map((team) => ({ team: team.slug, label: team.name, players: team.players.map((player) => ({ value: player.slug, label: player.displayName })), }))} /> ); }
π§Ί Carts
From @shopdiddo/react/cart. Every component goes inside a CartProvider. Put it near the top of your app so the cart follows your customer between pages. For a setup example, see step 4 of the React SDK guide.
Diddo creates the cart when your customer first adds something, but doesn't store its ID for you. Save it from onCartIdChange (in local storage, a cookie or your customer's account) and pass it back as cartId.
CartProvider props
| Prop | Type | Default | Description |
|---|---|---|---|
cartId | `string | null` | β |
onCartIdChange | `(cartId: string | null) => void` | β |
loadingFallback | ReactNode | Nothing | Shown while the cart starts up. |
cartId is read once, when the provider mounts. To switch to a different cart, change the provider's key.
Adding to the cart
A ProductDetail inside the CartProvider adds to the cart for you (see Product catalogs). To add from anywhere else, such as a featured product on your home page, use AddToCartButton:
typescriptimport { AddToCartButton } from '@shopdiddo/react/cart'; <AddToCartButton variantId={variant.id} quantity={1} onAdded={() => openCartDrawer()} />;
| Prop | Type | Default | Description |
|---|---|---|---|
variantId | string | β | The variant to add. |
quantity | number | 1 | How many to add. |
customizations | CartLineCustomization[] | β | Personalization, such as a name and number on a jersey. See Customizable Products. |
label | string | 'Add to cart' | The button's text. |
onAdded | (cart: Cart) => void | β | Called with the updated cart once the item is added. |
onError | (error: ParsedError) => void | β | Called if the item can't be added, for example because it's sold out. The button shows the problem too. |
disabled | boolean | false | Disables the button. |
fullWidth | boolean | false | Stretches the button to fill its container. |
className | string | β | Added to the button. |
The same variant added twice with different customizations becomes two separate lines in the cart, so your customer can change or remove each one on its own.
Showing the cart
CartLineList shows every line in the cart, with a quantity stepper and a remove button on each. CartSummary shows the subtotal, shipping, tax and total.
typescriptimport { CartLineList, CartSummary, CartEmptyState } from '@shopdiddo/react/cart'; function CartPage() { return ( <> <CartLineList emptyState={ <CartEmptyState message="Find something you love in our store."> <Link to="/products">Keep shopping</Link> </CartEmptyState> } /> <CartSummary /> </> ); }
Changes show straight away. If Diddo can't make a change, for example because there isn't enough stock, the line goes back to how it was and shows why. The stepper never goes above what's in stock or the most your customer can buy in one order.
| Component | Props | Description |
|---|---|---|
CartLineList | emptyState, className | Every line in the cart. While the cart loads it shows placeholders, and if loading fails it offers a retry. When the cart is empty it shows emptyState, which defaults to \<CartEmptyState /\>. Pass null to show nothing. |
CartEmptyState | title, message, children, className | The empty cart. title defaults to "Your cart is empty". Put a link back to your store in children. |
CartSummary | className | The cart's totals. |
π¬Shipping and tax depend on where your customer is sending their order. Until they choose a shipping method at checkout,
CartSummaryshows "Calculated at checkout" for both, and marks the total as an estimate.
Building blocks
CartLineList is made from these. Use them to lay out the cart your own way.
| Component | Props | Shows |
|---|---|---|
CartLine | lineId, className | One line: image, title, options, price, stepper and remove. |
CartQuantityStepper | lineId, onChange, onError, className | The quantity control for one line. |
Each takes the ID of a line in the cart. Get the IDs from useCart:
typescriptimport { CartLine, useCart } from '@shopdiddo/react/cart'; function CompactCart() { const { cart } = useCart(); return cart?.lines.map((line) => <CartLine key={line.id} lineId={line.id} />); }
Hooks
Use these inside a CartProvider to build your own cart UI.
useCart(): your customer's cart.
| Field | Type | Description |
|---|---|---|
cart | `Cart | null` |
cartId | `string | null` |
status | `'loading' | 'ready' |
error | `ParsedError | null` |
isEmpty | boolean | The cart has nothing in it. |
itemCount | number | The total quantity across all lines, for a badge. |
pendingLineIds | ReadonlySet\<string\> | Lines with a change on its way to Diddo. |
addLine | (input: CartAddInput) => Promise\<Cart\> | Adds a variant. Creates the cart if there isn't one yet. |
setLineQuantity | (lineId: string, quantity: number) => Promise\<Cart\> | Sets a line's quantity. 0 removes the line. |
removeLine | (lineId: string) => Promise\<Cart\> | Removes a line. |
refresh | () => Promise\<void\> | Reloads the cart, for example to retry after an error. |
CartAddInput is { variantId, quantity, customizations? }. Quantity changes show straight away and roll back if Diddo refuses them; the returned promise rejects with the reason.
useCartLine(lineId): one line, with line (null once it's removed), isPending, setQuantity(quantity) and remove().
useAddToCart(): for your own add-to-cart buttons. It returns addLine(input), isAdding, error and reset(). Each call to the hook keeps its own isAdding and error, so two buttons on one page each show only their own progress and errors.
π Checkout
From @shopdiddo/react. Use \<CheckoutPage\> for a complete checkout in one component (see step 5 of the React SDK guide), or build your own layout inside a CheckoutProvider.
\<CheckoutPage\> props
| Prop | Type | Default | Description |
|---|---|---|---|
legalLinks | { sellerTermsUrl: string; sellerPrivacyUrl: string } | Fanatics' terms and privacy policy | The seller's terms of service and privacy policy, linked in the legal text under the payment options. Pass your own if you sell through a different seller. |
cartId | string | The CartProvider's cart | The cart to check out. Leave it out inside a CartProvider. |
buyer | CustomerParams | β | Details to fill in for your customer. See CheckoutProvider props below. |
returnUrl | string | β | Where the receipt's "Continue Shopping" button goes. The button is hidden without it. |
header | ReactNode | β | Shown at the top while your customer pays, for example your logo. Hidden on the receipt, which shows your theme's logo. |
visaEnabled | boolean | false | Shows "Pay with Visa". See Taking payment below. |
cartId and buyer are read once, when the page mounts.
Building your own checkout
Put a CheckoutProvider inside your CartProvider and it checks out your customer's cart. Then add the components you want:
typescriptimport { CheckoutProvider, BuyerSummary, OrderSummary, PaymentOptions, LegalCopy, PaymentErrorModal, Receipt, } from '@shopdiddo/react'; function CheckoutRoute() { return ( <CheckoutProvider loadingFallback={<Spinner />}> <BuyerSummary /> <OrderSummary /> <PaymentOptions onSuccess={() => analytics.track('order_placed')} /> <LegalCopy sellerTermsUrl="https://example.com/terms" sellerPrivacyUrl="https://example.com/privacy" /> <PaymentErrorModal /> <Receipt returnUrl="https://example.com" /> </CheckoutProvider> ); }
You don't need to show or hide anything yourself. Each component knows where the payment is up to: the payment options hide once your customer has paid, and the receipt appears.
π¬ Until your customer adds their first item, there's no cart yet, so the checkout shows nothing.
Checking out a cart you created
If you create carts with the Diddo API rather than a CartProvider, pass the cart's ID:
typescript<CheckoutProvider cartId={cartId} loadingFallback={<Spinner />}>β¦</CheckoutProvider>
See Working with Carts for creating carts.
CheckoutProvider props
| Prop | Type | Default | Description |
|---|---|---|---|
cartId | string | The CartProvider's cart | The cart to check out. Leave it out inside a CartProvider. |
buyer | CustomerParams | β | Details to fill in for your customer: email, firstName, lastName, phone, country. Details from their CST take priority. |
loadingFallback | ReactNode | Nothing | Shown while the checkout starts and the cart loads. |
cartId and buyer are read once, when the provider mounts. To check out a different cart, change the provider's key.
Order details
| Component | Props | Shows |
|---|---|---|
BuyerSummary | β | Your customer's name and email, when Diddo knows them. |
OrderSummary | β | The items, with subtotal, shipping, tax and total. |
CompactOrderTotal | label (required) | The total on one line, such as "Your cart total: $84.00". |
LegalCopy | sellerTermsUrl, sellerPrivacyUrl | The legal text under the payment options, linking to the seller's terms of service and privacy policy, and to Diddo's terms. |
PoweredByDiddo | β | A "Secure checkout powered by Diddo" line. |
Taking payment
PaymentOptions shows every payment method available to your customer: Apple Pay or Google Pay when their device supports it, and a card form that collects their shipping address and card details.
| Prop | Type | Description |
|---|---|---|
onSuccess | (billingDetails?: BillingDetails, meta?: { confirming?: boolean }) => void | Called once the payment succeeds, whichever method your customer used. |
onError | (error: ParsedError) => void | Called when a payment fails. PaymentErrorModal shows the error to your customer, so use this for your own logging. |
visaEnabled | boolean | Shows "Pay with Visa". See below. |
consumerPrompt | string | The text on Visa's pay button. Defaults to "Complete payment". |
billingDetails contains the name, email, phone and address your customer paid with, when the payment method provides them. If meta.confirming is true, the payment went through but Diddo hadn't finished confirming the order within a minute. The order is almost certainly placed, and the receipt says so.
Add PaymentErrorModal once inside the CheckoutProvider. It shows your customer what went wrong with a payment, such as a declined card, and lets them try again.
πPay with Visa: contact Diddo to enable Visa payments for your account, then pass
visaEnabled. "Pay with Visa" only appears when your customer's CST was minted with their email, and their device supports passkeys.
To lay out payment methods yourself, use the methods PaymentOptions is made from. Each takes onSuccess and onError:
| Component | Shows |
|---|---|
ExpressCheckout | Apple Pay and Google Pay buttons. onBusyChange tells you when the wallet is processing. |
CardPaymentForm | The card form: shipping address first, then card details. Set backButton={false} to hide its Back button if you provide your own. |
VisaIccPayment | "Pay with Visa". Takes consumerPrompt. |
After payment
Receipt appears once the payment is confirmed. It shows your logo, the items, the total and a "Continue Shopping" button.
| Prop | Type | Default | Description |
|---|---|---|---|
returnUrl | `string | null` | β |
logoSrc | string | Your theme's logo | The logo at the top of the receipt. |
thanksMessage | string | β | Replaces the thank-you message. |
showItems | boolean | true | Lists the items. |
showDeliveryAddress | boolean | false | Shows the delivery address. |
shippingAddress | `ShippingAddress | null` | β |
totalLabel | string | 'Total' | The label for the total. |
zeroValueDisplay | string | '$0' | What to show for an amount of zero. |
compact | boolean | false | A smaller layout. |
While the order is being confirmed, OrderStatus shows your customer progress messages that change the longer it takes. Pass stages to use your own messages: each stage is { afterMs, text }, shown from afterMs milliseconds onwards. The defaults are in DEFAULT_ORDER_STATUS_STAGES.
Hooks
useCheckoutStatus(): where the payment is up to, for your own UI, such as hiding your app's navigation while your customer pays.
| Field | Type | Description |
|---|---|---|
status | `'idle' | 'paying' |
error | `ParsedError | null` |
unverified | boolean | The payment is confirmed, but Diddo couldn't verify the order in time. |
typescriptimport { useCheckoutStatus } from '@shopdiddo/react'; function CheckoutHeader() { const { status } = useCheckoutStatus(); if (status === 'confirming') return <p>Placing your orderβ¦</p>; return <BackToCartLink />; }
π¨ Theming
Creating a theme
createTheme merges your changes onto Diddo's defaults. Pass the result to DiddoProvider's theme. For an example, see step 6 of the React SDK guide.
π¬ Create your theme once, outside your components. Creating it on every render restyles every Diddo component each time.
To build a theme in parts, use defineTheme. It checks your types without merging anything, so you can combine the parts in createTheme later:
typescriptimport { createTheme, defineTheme } from '@shopdiddo/react'; const brandColors = defineTheme({ colors: { brand: '#e63946', brandStrong: '#c1121f' } }); const brandType = defineTheme({ typography: { fontFamily: '"Inter", system-ui, sans-serif' } }); export const myTheme = createTheme({ ...brandColors, ...brandType });
createTheme takes an optional second argument, the theme to build on, which defaults to Diddo's defaultTheme. Use it for variations of your own theme, such as a dark mode:
typescriptexport const myDarkTheme = createTheme({ colors: { surface: '#111', text: '#f5f5f5' } }, myTheme);
Sharing the theme with your own components
DiddoProvider's theme covers every Diddo component inside it. If your own components need the theme's values too, wrap them in a ThemeProvider and read the theme with useTheme():
typescriptimport { ThemeProvider, useTheme } from '@shopdiddo/react'; function CheckoutHeader() { const theme = useTheme(); return <img src={theme.assets.logo} alt={theme.assets.logoAlt} />; } <ThemeProvider theme={myTheme}> <DiddoProvider token={cst}> <CheckoutHeader /> β¦ </DiddoProvider> </ThemeProvider>;
| Prop | Type | Default | Description |
|---|---|---|---|
theme | `Theme | PartialTheme` | Diddo theme |
applyGlobalStyles | boolean | false | Also applies the theme's font and pageBackground to \<body\>. Leave it off if your app styles its own page. |
className | string | β | Added to the theme's wrapper. The wrapper doesn't affect your layout, so use this for CSS variables, not sizing. |
β οΈPut any
ThemeProvideroutside yourCheckoutProvider, never between it and the payment components. The card fields take their styling from the theme around theCheckoutProvider, so a different theme inside it splits the payment form across two styles.
Colors
| Token | Default | Styles |
|---|---|---|
brand | #6366f1 | Buttons, links and selected controls. Also the card fields' main color. |
brandStrong | #4f46e5 | A deeper brand shade, used where buttons blend into it. |
onBrand | #ffffff | Text on top of brand, such as button labels. |
surface | #ffffff | Cards and panels. |
surfaceElevated | #ffffff | Dialogs and sheets that sit above surface. |
text | #1a1a1a | Body text. |
textMuted | #64748b | Secondary text. |
border | rgba(0,0,0,0.1) | Thin borders. |
borderStrong | rgba(0,0,0,0.2) | Focus rings and stronger dividers. |
accentPrimary | #1a1a1a | Main text on the receipt and the order-confirmed screen. |
accentSecondary | #1a1a1a | Labels and headings on the receipt. |
accentMuted | #888 | Faded text on the receipt. |
glassBg | rgba(0,0,0,0.04) | The background behind item images. |
confirmedIconColor | #1a1a1a | The check mark on the order-confirmed screen. |
Typography
| Token | Default | Styles |
|---|---|---|
fontFamily | "Satoshi", BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif | All text. |
fontFamilyDisplay | Same as fontFamily | Large headings, such as the receipt total and dialog titles. |
letterSpacing | normal | All text. |
The SDK's stylesheet includes the Satoshi font. To use your own font, load it in your app as usual, then set fontFamily.
Layout
| Token | Default | Styles |
|---|---|---|
containerMaxWidth | 960px | The checkout's maximum width. |
pageBackground | #f9fafb | The page background, only with applyGlobalStyles. |
scrollbarWidth | 5px | Scrollbars inside Diddo components. |
scrollbarThumb | rgba(0,0,0,0.12) | Scrollbar color. |
scrollbarThumbHover | rgba(0,0,0,0.18) | Scrollbar color on hover. |
Animations
animations is 'rich' (the default) or 'minimal'. 'rich' adds the animated order-confirmed screen and the receipt's entrance; 'minimal' shows still versions. If your customer has asked their device to reduce motion, Diddo always uses 'minimal'. Your own components can follow the same rule with useAnimationMode(), which returns the mode Diddo is actually using.
Assets
| Token | Description |
|---|---|
logo | The logo on the receipt: a URL, or an image your bundler imports. |
logoAlt | The logo's alt text. |
Component tokens
To change one part of one component, set its tokens under components, grouped by area:
typescriptconst myTheme = createTheme({ components: { checkout: { submit: { background: '#e63946' } }, products: { card: { radius: '4px' }, pill: { selectedBackground: '#e63946' } }, cart: { summary: { border: 'none' } }, }, });
| Area | Group | Tokens | Styles |
|---|---|---|---|
products | card | background, radius, shadow, shadowHover | Product cards. |
products | grid | gap, minColumnWidth | The product grid. It fits as many columns as it can at minColumnWidth. |
products | pill | background, selectedBackground, selectedColor, radius | Option and filter chips. |
products | carousel | radius, aspectRatio, dotColor, dotColorActive | The product image gallery. |
cart | line | gap, imageSize, radius | Cart lines. |
cart | summary | background, radius, border | The cart totals. |
checkout | submit | background, shadow, shadowHover, backgroundDisabled, textDisabled | The pay button. |
checkout | cardButton | height, borderRadius, outlineColor, boxShadow, boxShadowHover, boxShadowActive | Payment method buttons. |
checkout | sheet | background, shadow, footerBackground | The card form and other sheets. |
checkout | shipping | selectedBackground | The selected shipping option. |
checkout | receipt | grandTotalFontSize, grandTotalFontWeight, totalsRowFontWeight, totalsRowOpacity, orderTotalPaddingTop, itemNameWhiteSpace, shadow, ctaBackground, ctaBackgroundHover | The receipt. |
checkout | overlay | titleFontSize, iconSize, iconBackground, staticCheckBg, aurora1, aurora2, accentGradient, ringGlow | The order-confirmed screen. |
checkout | errorPage | background | The full-page error screen. |
Every token takes a CSS value. To see the defaults, log defaultTheme.components.
π¬Dark themes: besides the colors, set
components.checkout.sheet.shadowandcomponents.checkout.receipt.shadowto'none'. The default shadows are designed for light backgrounds.
Card and wallet payments
The card fields and the Apple Pay and Google Pay buttons are drawn by Stripe, so they're styled through components.checkout.stripe. appearance follows Stripe's Appearance API, except for variables Stripe has deprecated. Its colorPrimary follows your colors.brand unless you set it.
typescriptconst myTheme = createTheme({ components: { checkout: { stripe: { appearance: { variables: { borderRadius: '4px', fontSizeBase: '15px', iconColor: '#555' }, }, expressCheckout: { buttonHeight: 48, buttonTheme: { applePay: 'black', googlePay: 'black' }, }, }, }, }, });
| Token | Description |
|---|---|
appearance | theme, variables, rules, labels, inputs and disableAnimations, as in Stripe's Appearance API. |
expressCheckout | buttonHeight in pixels, and buttonTheme for applePay ('black', 'white' or 'white-outline') and googlePay ('black' or 'white'). |
Matching your own components
To make your own components match a Diddo theme, use these CSS variables inside any Diddo provider. Their values come from the active theme.
| Variable | Theme token |
|---|---|
--dc-brand, --dc-brand-strong, --dc-on-brand | colors.brand, colors.brandStrong, colors.onBrand |
--dc-surface, --dc-surface-elevated | colors.surface, colors.surfaceElevated |
--dc-text, --dc-text-muted | colors.text, colors.textMuted |
--dc-border, --dc-border-strong | colors.border, colors.borderStrong |
--dc-font-family, --dc-font-family-display | typography.fontFamily, typography.fontFamilyDisplay |
--dc-letter-spacing | typography.letterSpacing |
css.my-banner { background: var(--dc-brand); color: var(--dc-on-brand); font-family: var(--dc-font-family); }
Only rely on the variables in this table. Diddo's components use others, but those can change in any release.
