Component Differences with PWA Kit
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.
| 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 |
| 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) |
PWA Kit:
Storefront Next:
PWA Kit (Theme-based):
Storefront Next (CVA):
- 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)
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".
| 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 |
- Convert PropTypes to TypeScript interfaces.
- Add explicit return types to functions.
- Use
React.ComponentProps<'element'>for HTML element props. - Replace
PropTypes.shape({...})withinterfacedefinitions.