Building sections
Styling
Tailwind v4, colour schemes, transitions, fonts.
Tailwind v4. Theme settings become CSS variables, which become Tailwind tokens:
merchant picks a colour → --theme-background → --color-background → bg-backgroundSo bg-background and text-foreground follow whatever colour scheme the
section is set to, with no rebuild when a merchant changes colours.
Colour schemes
Four ship by default; merchants can edit or add more. Every section has a picker,
and <body> uses the theme default. Each scheme emits a .color-scheme-n class
that sets those variables.
To add a role:
- Add a
colorto thedefinitionarray insettings_schema.json - Map it in
role - Emit a
--theme-*variable incss-variables.liquid - Add a matching token to
@themeinmain.css
The
roleobject needs all ten keys —background,text,primary_button,secondary_button,primary_button_border,secondary_button_border,on_primary_button,on_secondary_button,links,icons. A partial object is rejected.
Transitions
Anything that appears and disappears gets one. Use CSS plus usePresence:
1const { mounted, visible } = usePresence(open, 300)2if (!mounted) return null34<div className={`transition-opacity duration-300 ease-out motion-reduce:transition-none ${5 visible ? 'opacity-100' : 'opacity-0'6}`} />if (!open) return null makes exit animations impossible — the element is gone
before anything can run. usePresence splits mounted (in the DOM) from
visible (in its open state) and holds the mount long enough to animate out.
Three rules:
- the duration must match the CSS
- add
motion-reduce:transition-none - set
pointerEvents: 'none'while animating out, so the fading element doesn't eat clicks
Currently 300ms for drawers, 150ms for dropdowns.
Icons
Lucide. Import by name, never the namespace:
1import { ShoppingBag } from 'lucide-react' // yes2import * as Icons from 'lucide-react' // no — pulls in all of themIcons inherit currentColor, so they follow the colour scheme for free. Mark
them aria-hidden and put the accessible name on the button or link.
Fonts
Three sources under Typography: Shopify's library, your own files, or an external
service. Custom font fields accept either a theme asset filename
(brand.woff2, dropped in src/fonts/) or a full URL from Content → Files.
Shopify has no file-upload setting type —
image_pickerandvideofilter by media type and won't offer a font. A text field is the only option, which is why it accepts both.
Motion
usePrefersReducedMotion() tracks the visitor's setting live, rather than
reading it once — the OS setting can be toggled while the page is open. Anything
that moves on its own should check it. Carousels do this for you.