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.

AspectPWA KitStorefront Next
LanguageJavaScript + PropTypesTypeScript
Type CheckingRuntime (PropTypes)Compile-time (TypeScript)
Component LibraryChakra UI (runtime)shadcn/ui (copied source)
Styling ApproachCSS-in-JS (Emotion)Utility classes (Tailwind)
Variant SystemTheme configClass Variance Authority
PrimitivesChakra componentsRadix UI headless
AspectPWA Kit (Chakra)Storefront Next (Tailwind)
Inline styles<Box padding="4" bg="blue.500"><div className="p-4 bg-blue-500">
Responsivedisplay={{base: 'none', lg: 'block'}}className="hidden lg:block"
Hover states_hover={{bg: 'blue.600'}}className="hover:bg-blue-600"
Theme tokenscolor="gray.500"className="text-gray-500"
Component stylesuseMultiStyleConfig('Button')buttonVariants({ variant, size })
Style overrideProps 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 ...rest props 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" to className="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 ComponentStorefront Next EquivalentNotes
Layout Primitives
Box<div> with TailwindUse 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 TailwindUse text-*, font-* classes
Heading<h1>-<h6> with TailwindUse 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
LinkReact 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">
useNumberInputCustom implementationNo direct equivalent
Feedback
Alert<Alert> (ui/alert)
AlertIconIcon 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)
useToastCustom toast implementationOr use sonner/react-hot-toast
Overlay
Modal<Dialog> (ui/dialog)Radix-based
ModalOverlayBuilt into <Dialog>Automatic overlay
ModalContent<DialogContent> (ui/dialog)
ModalHeader<DialogHeader> (ui/dialog)
ModalBody<div> inside DialogContent
ModalFooter<DialogFooter> (ui/dialog)
ModalCloseButtonBuilt into <DialogContent>Optional showCloseButton prop
AlertDialog<AlertDialog> (ui/alert-dialog)Radix-based
AlertDialogOverlayBuilt into <AlertDialog>
AlertDialogContent<AlertDialogContent>
AlertDialogHeader<AlertDialogHeader>
AlertDialogFooter<AlertDialogFooter>
AlertDialogBody<AlertDialogDescription>
Drawer<Drawer> (ui/drawer)Vaul-based, or use <Sheet>
DrawerOverlayBuilt 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 stylingNo separate component
PopoverBody<div> inside PopoverContent
PopoverFooter<div> with stylingNo separate component
PopoverArrowCSS-basedTailwind arrow styling
PopoverCloseButton<Button> with X icon
Tooltip<Tooltip> (ui/tooltip)Radix-based
PortalReact Portal or Radix PortalBuilt into overlay components
Disclosure
Accordion<Accordion> (ui/accordion)Radix-based
AccordionItem<AccordionItem>
AccordionButton<AccordionTrigger>
AccordionPanel<AccordionContent>
AccordionIconBuilt into <AccordionTrigger>Chevron icon included
useDisclosureReact state or Radix open stateconst [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 TailwindUse object-cover, rounded-*
Avatar<Avatar> (ui/avatar)Radix-based
List<ul> or <ol>
ListItem<li>
Card<Card> (ui/card)Composition pattern
Other
IconLucide React iconsimport { IconName } from 'lucide-react'
VisuallyHidden<span className="sr-only">Screen reader only
FadeCSS transitionstransition-opacity with conditional opacity-0
Hooks
useMultiStyleConfigCVA + cn()Define variants in component
useStyleConfigCVA + cn()
useThemeTailwind CSS variablesAccess via var(--color-*)
useMediaQuerywindow.matchMedia()Or use Tailwind breakpoints
useBreakpointTailwind breakpointsResponsive classes preferred
useBreakpointValueTailwind breakpointsUse responsive class syntax
useRadio / useRadioGroupRadix RadioGroupBuilt into component
useStylesNot neededStyles are class-based
createStylesContextReact ContextOr pass classes as props
StylesProviderReact ContextOr use Tailwind directly
extendThemetailwind.config.jsExtend Tailwind theme
ChakraProviderNot neededTailwind 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({...}) with interface definitions.