Theme SDK Reference
Author canonical theme sections on the shared surface foundation.
Theme SDK Reference
Use @commercit/theme-sdk for theme-owned, workspace-editable sections.
Theme sections and app sections now share:
- one UI foundation
- one section runtime
- one binding model
- one Storefront Section Data Contract
They differ only in ownership and lifecycle:
- theme sections are editable in the workspace
- app sections are installable and read-only at the code level
Imports
import {
defineSection,
formatPrice,
t,
useBoundAction,
useBoundData,
useLocale,
useMarket,
useThemeTokens,
} from '@commercit/theme-sdk'
import {
Badge,
Button,
Card,
CardContent,
CardHeader,
CardTitle,
Container,
SectionShell,
Stack,
} from '@commercit/theme-sdk/ui'
defineSection
Each section also has a sibling section.meta.ts file. The meta file must
export sectionContract; this is the public data contract used by published
storefronts, preview, AI, and app sections. Sections must not read raw
pageContext or undeclared product/shop/page fields.
import {
SECTION_CONTRACT_VERSION,
defineStorefrontDataBlocks,
defineStorefrontSectionManifest,
type StorefrontSectionProps,
} from '@commercit/theme-sdk'
export const sectionContract = defineStorefrontSectionManifest({
contractVersion: SECTION_CONTRACT_VERSION,
slug: 'shared-header',
region: 'header',
renderMode: 'interactive',
capabilities: ['cart.open', 'search.open', 'search.query', 'ui.state'],
data: defineStorefrontDataBlocks([
{ type: 'shopIdentity', fields: ['name', 'logo'] },
{ type: 'navigation', menu: 'main', fields: ['label', 'url', 'children'] },
{ type: 'languageSwitcher' },
{ type: 'cartShell' },
] as const),
})
export type SharedHeaderProps = StorefrontSectionProps<typeof sectionContract>
import { defineMessages, defineSection, formatPrice, t } from '@commercit/theme-sdk'
import { Button, Card, CardContent, CardHeader, CardTitle } from '@commercit/theme-sdk/ui'
const copy = defineMessages('editorial', {
heading: 'Editorial feature',
cta: 'Explore',
body: 'Orders over {threshold} qualify for premium delivery messaging.',
})
export default defineSection({
messageCatalog: Object.values(copy),
component: function EditorialPromo({ config }) {
return (
<Card>
<CardHeader>
<CardTitle>{String(config.heading ?? t(copy.heading))}</CardTitle>
</CardHeader>
<CardContent>
<p className="text-sm text-muted-foreground">
{t(copy.body, { threshold: formatPrice(50) })}
</p>
<Button>{String(config.cta_label ?? t(copy.cta))}</Button>
</CardContent>
</Card>
)
},
})
New theme sections should emit extracted messageCatalog entries via defineMessages(...) and optional msg(...). Packaged theme copy stays language-based in code; market-specific overrides belong to the storefront localization engine.
Shared UI primitives
Use @commercit/theme-sdk/ui instead of raw internal imports for new theme sections.
Public primitives include:
Button,BadgeCard,CardHeader,CardTitle,CardDescription,CardContent,CardFooterInput,Textarea,Label,Select,Checkbox,RadioGroup,Switch,SliderAccordion,Tabs,DropdownMenuDialog,Drawer,Sheet,Popover,TooltipSkeleton,Progress,CarouselSectionShell,Container,Stack,Inline,Grid
useThemeTokens
import { useThemeTokens } from '@commercit/theme-sdk'
export default function Example() {
const tokens = useThemeTokens()
return (
<div
style={{
color: tokens.colors.text,
background: tokens.colors.surface,
borderColor: tokens.colors.border,
}}
>
Shared theme-aware UI
</div>
)
}
Storefront Localization Helpers
Theme sections receive host-resolved storefront localization context.
Available helpers:
useTranslations()t(descriptor, params?)useLocale()useMarket()useCurrency()useDirection()formatPrice(amount, options?)formatDate(value, options?)formatNumber(value, options?)
Example:
import { defineMessages, defineSection, formatPrice, t, useLocale, useMarket } from '@commercit/theme-sdk'
import { Card, CardContent, CardHeader, CardTitle } from '@commercit/theme-sdk/ui'
const copy = defineMessages('promo', {
heading: 'Free shipping',
body: 'Orders over {threshold} ship free.',
})
export default defineSection({
messageCatalog: Object.values(copy),
component: function PromoStrip() {
const locale = useLocale()
const market = useMarket()
return (
<Card>
<CardHeader>
<CardTitle>{t(copy.heading)}</CardTitle>
</CardHeader>
<CardContent>
<p>{t(copy.body, { threshold: formatPrice(50) })}</p>
<p className="text-sm text-muted-foreground">
locale: {locale} · market: {market.marketHandle ?? 'default'}
</p>
</CardContent>
</Card>
)
},
})
See Storefront Localization Engine for market assignment, fallback rules, and merchant overrides.
useBoundData and useBoundAction
Theme sections can consume both core and app-owned runtime surfaces through host bindings.
import { defineSection, useBoundAction, useBoundData } from '@commercit/theme-sdk'
import { Button, Card, CardContent, CardHeader, CardTitle, Stack } from '@commercit/theme-sdk/ui'
type ReviewSummary = {
average_rating?: number
review_count?: number
}
export default defineSection({
component: function ProductReviewSummary({ config }) {
const summary = useBoundData<ReviewSummary>('summary') ?? {}
const openReviews = useBoundAction('open_reviews')
return (
<Card>
<CardHeader>
<CardTitle>{String(config.heading ?? 'What shoppers think')}</CardTitle>
</CardHeader>
<CardContent>
<Stack gap="sm">
<p className="text-3xl font-semibold">
{typeof summary.average_rating === 'number' ? summary.average_rating.toFixed(1) : '—'}★
</p>
<p className="text-sm text-muted-foreground">
{typeof summary.review_count === 'number' ? summary.review_count : 0} verified reviews
</p>
<div>
<Button onClick={() => void openReviews({ path: '/pages/reviews' })}>
Read all reviews
</Button>
</div>
</Stack>
</CardContent>
</Card>
)
},
})
Runtime rules:
- bind by unified surface
moduleId - never bind by slug
- data is read through
useBoundData(alias) - actions are invoked through
useBoundAction(alias)
AI-generated custom sections
When AI creates a workspace section that depends on app outputs:
inspect({ what: "surfaces" })finds the exactmoduleId- the generated section imports from
@commercit/theme-sdk/@commercit/theme-sdk/ui - the write call includes a
bindingspayload - the host stores the binding contract in
settings._bindings
This keeps the section runtime unified without changing canonical section persistence.
What does not belong in the theme SDK
Theme SDK must not expose app-only concerns:
- app/admin capabilities
- app storage
- secrets
- jobs
- events
- App API access
For those, use @commercit/app-sdk.