PWA Kit components are typically JavaScript + PropTypes with Chakra primitives and style props. Storefront Next components are TypeScript-first and generally composed from Radix/shadcn patterns with utility-class styling and CVA-based variants. The main shift is from runtime styling and theme objects to compile-time class composition and typed component contracts.
Component Architecture
| Aspect | PWA Kit | Storefront Next |
|---|---|---|
| Language | JavaScript + PropTypes | TypeScript |
| Type Checking | Runtime (PropTypes) | Compile-time (TypeScript) |
| Component Library | Chakra UI (runtime) | shadcn/ui (copied source) |
| Styling Approach | CSS-in-JS (Emotion) | Utility classes (Tailwind) |
| Variant System | Theme config | Class Variance Authority |
| Primitives | Chakra components | Radix UI headless |
Styling Comparison
| Aspect | PWA Kit (Chakra) | Storefront Next (Tailwind) |
|---|---|---|
| Inline styles | <Box padding="4" bg="blue.500"> |
<div className="p-4 bg-blue-500"> |
| Responsive | display={{base: 'none', lg: 'block'}} |
className="hidden lg:block" |
| Hover states | _hover={{bg: 'blue.600'}} |
className="hover:bg-blue-600" |
| Theme tokens | color="gray.500" |
className="text-gray-500" |
| Component styles | useMultiStyleConfig('Button') |
buttonVariants({ variant, size }) |
| Style override | Props spread: {...styles.container} |
Class merge: cn(baseClass, className) |
Responsive Design Patterns
PWA Kit:
// Object syntax for breakpoints
<Box display={{base: 'block', md: 'flex', lg: 'grid'}}>
// Array syntax (base, sm, md, lg, xl)
<Grid templateColumns={['1fr', '1fr', 'repeat(2, 1fr)', 'repeat(4, 1fr)']}>
// Hook-based
const [isDesktop] = useMediaQuery('(min-width: 992px)')
Storefront Next:
// Breakpoint prefixes (mobile-first)
<div className="block md:flex lg:grid">
// Responsive grid
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4">
// JavaScript detection
const isDesktop = typeof window !== 'undefined' &&
window.matchMedia('(min-width: 1024px)').matches;
Component Variants
PWA Kit (Theme-based):
// In theme/components/button.js
export default {
baseStyle: {...},
variants: {
solid: {...},
outline: {...},
},
sizes: {
sm: {...},
md: {...},
}
}
// Usage
<Button variant="solid" size="md">Click</Button>
Storefront Next (CVA):
// In component file
const buttonVariants = cva("base-classes", {
variants: {
variant: { solid: "...", outline: "..." },
size: { sm: "...", md: "..." },
},
});
// Usage
<Button variant="solid" size="md">
Click
</Button>;
Key Similarities
- Composition over inheritance: Both use functional components composed from primitives
- Props passthrough: Both spread
...restprops to underlying elements - Responsive-first: Both prioritize mobile-first responsive design
- Variant systems: Both support component variants for consistent styling
- Accessibility: Both build on accessible component foundations (Chakra/Radix)
- HOC patterns: Both use HOCs for cross-cutting concerns (auth, suspense)
Conversion Considerations
Here are some tips for moving from Chakra to Tailwind.
- Style props vs. Utility classes: Convert
padding="4"toclassName="p-4". - Theme tokens vs. Tailwind config: Map color/spacing scales.
- useMultiStyleConfig vs. CVA: Convert component style objects to variant functions.
- Responsive objects vs. breakpoint prefixes: Replace
{{base: 'x', lg: 'y'}}with"x lg:y".
Component Library Conversion
| Chakra Component | Storefront Next Equivalent | Notes |
|---|---|---|
| Layout Primitives | ||
Box |
<div> with Tailwind |
Use className="..." for styling |
Flex |
<div className="flex"> |
Add flex-row, flex-col, gap-* as needed |
HStack |
<div className="flex flex-row gap-*"> |
Horizontal stack |
VStack |
<div className="flex flex-col gap-*"> |
Vertical stack |
Stack |
<div className="flex gap-*"> |
Use flex-row or flex-col |
StackDivider |
<Separator> (ui/separator) |
Or use divide-y/divide-x classes |
Center |
<div className="flex items-center justify-center"> |
|
Container |
<div className="container mx-auto"> |
|
Grid |
<div className="grid"> |
Add grid-cols-*, gap-* |
GridItem |
<div> |
Use col-span-*, row-span-* |
SimpleGrid |
<div className="grid grid-cols-*"> |
|
Wrap |
<div className="flex flex-wrap"> |
|
WrapItem |
<div> |
Direct child of flex-wrap |
Spacer |
<div className="flex-1"> |
Or use gap-* on parent |
AspectRatio |
<AspectRatio> (ui/aspect-ratio) |
Radix-based |
Divider |
<Separator> (ui/separator) |
Radix-based |
| Typography | ||
Text |
<p>, <span> with Tailwind |
Use text-*, font-* classes |
Heading |
<h1>-<h6> with Tailwind |
Use text-*, font-bold |
| Buttons & Links | ||
Button |
<Button> (ui/button) |
CVA variants: default, destructive, outline, secondary, ghost, link |
ButtonGroup |
<div className="flex gap-2"> |
Group buttons manually |
IconButton |
<Button size="icon"> |
Use icon size variant |
Link |
React Router <Link> |
Or <a> with Tailwind |
CloseButton |
<Button size="icon" variant="ghost"> |
With X icon |
| Form Controls | ||
Input |
<Input> (ui/input) |
|
InputGroup |
<div className="relative"> |
Position elements manually |
InputLeftElement |
<div className="absolute left-3"> |
Position inside relative parent |
InputRightElement |
<div className="absolute right-3"> |
Position inside relative parent |
Select |
<SelectNative> (ui/select-native) |
Native select element |
Checkbox |
<Checkbox> (ui/checkbox) |
Radix-based |
Radio |
<RadioGroup> (ui/radio-group) |
Radix-based |
RadioGroup |
<RadioGroup> (ui/radio-group) |
Radix-based |
FormControl |
<div> |
Use <Label> + input + error message |
FormLabel |
<Label> (ui/label) |
|
FormErrorMessage |
<p className="text-destructive text-sm"> |
|
useNumberInput |
Custom implementation | No direct equivalent |
| Feedback | ||
Alert |
<Alert> (ui/alert) |
|
AlertIcon |
Icon inside <Alert> |
Use Lucide icons |
AlertTitle |
<AlertTitle> (ui/alert) |
|
AlertDescription |
<AlertDescription> (ui/alert) |
|
Spinner |
<div className="animate-spin"> |
Or a custom Spinner component |
Skeleton |
<Skeleton> (ui/skeleton) |
|
useToast |
Custom toast implementation | Or use sonner/react-hot-toast |
| Overlay | ||
Modal |
<Dialog> (ui/dialog) |
Radix-based |
ModalOverlay |
Built into <Dialog> |
Automatic overlay |
ModalContent |
<DialogContent> (ui/dialog) |
|
ModalHeader |
<DialogHeader> (ui/dialog) |
|
ModalBody |
<div> inside DialogContent |
|
ModalFooter |
<DialogFooter> (ui/dialog) |
|
ModalCloseButton |
Built into <DialogContent> |
Optional showCloseButton prop |
AlertDialog |
<AlertDialog> (ui/alert-dialog) |
Radix-based |
AlertDialogOverlay |
Built into <AlertDialog> |
|
AlertDialogContent |
<AlertDialogContent> |
|
AlertDialogHeader |
<AlertDialogHeader> |
|
AlertDialogFooter |
<AlertDialogFooter> |
|
AlertDialogBody |
<AlertDialogDescription> |
|
Drawer |
<Drawer> (ui/drawer) |
Vaul-based, or use <Sheet> |
DrawerOverlay |
Built into <Drawer> |
|
DrawerContent |
<DrawerContent> |
|
DrawerHeader |
<DrawerHeader> |
|
DrawerBody |
<div> inside DrawerContent |
|
DrawerFooter |
<DrawerFooter> |
|
DrawerCloseButton |
<DrawerClose> |
|
Popover |
<Popover> (ui/popover) |
Radix-based |
PopoverTrigger |
<PopoverTrigger> |
|
PopoverContent |
<PopoverContent> |
|
PopoverHeader |
<div> with styling |
No separate component |
PopoverBody |
<div> inside PopoverContent |
|
PopoverFooter |
<div> with styling |
No separate component |
PopoverArrow |
CSS-based | Tailwind arrow styling |
PopoverCloseButton |
<Button> with X icon |
|
Tooltip |
<Tooltip> (ui/tooltip) |
Radix-based |
Portal |
React Portal or Radix Portal | Built into overlay components |
| Disclosure | ||
Accordion |
<Accordion> (ui/accordion) |
Radix-based |
AccordionItem |
<AccordionItem> |
|
AccordionButton |
<AccordionTrigger> |
|
AccordionPanel |
<AccordionContent> |
|
AccordionIcon |
Built into <AccordionTrigger> |
Chevron icon included |
useDisclosure |
React state or Radix open state | const [open, setOpen] = useState(false) |
| Navigation | ||
Breadcrumb |
<Breadcrumb> (ui/breadcrumb) |
|
BreadcrumbItem |
<BreadcrumbItem> |
|
BreadcrumbLink |
<BreadcrumbLink> |
|
Menu (dropdown) |
<DropdownMenu> (ui/dropdown-menu) |
Radix-based |
| Navigation Menu | <NavigationMenu> (ui/navigation-menu) |
Radix-based |
| Data Display | ||
Badge |
<Badge> (ui/badge) |
CVA variants |
Image / Img |
<img> with Tailwind |
Use object-cover, rounded-* |
Avatar |
<Avatar> (ui/avatar) |
Radix-based |
List |
<ul> or <ol> |
|
ListItem |
<li> |
|
Card |
<Card> (ui/card) |
Composition pattern |
| Other | ||
Icon |
Lucide React icons | import { IconName } from 'lucide-react' |
VisuallyHidden |
<span className="sr-only"> |
Screen reader only |
Fade |
CSS transitions | transition-opacity with conditional opacity-0 |
| Hooks | ||
useMultiStyleConfig |
CVA + cn() |
Define variants in component |
useStyleConfig |
CVA + cn() |
|
useTheme |
Tailwind CSS variables | Access via var(--color-*) |
useMediaQuery |
window.matchMedia() |
Or use Tailwind breakpoints |
useBreakpoint |
Tailwind breakpoints | Responsive classes preferred |
useBreakpointValue |
Tailwind breakpoints | Use responsive class syntax |
useRadio / useRadioGroup |
Radix RadioGroup | Built into component |
useStyles |
Not needed | Styles are class-based |
createStylesContext |
React Context | Or pass classes as props |
StylesProvider |
React Context | Or use Tailwind directly |
extendTheme |
tailwind.config.js |
Extend Tailwind theme |
ChakraProvider |
Not needed | Tailwind is build-time |
TypeScript Adoption
- Convert PropTypes to TypeScript interfaces.
- Add explicit return types to functions.
- Use
React.ComponentProps<'element'>for HTML element props. - Replace
PropTypes.shape({...})withinterfacedefinitions.