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.

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 ...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)

Conversion Considerations

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".

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({...}) with interface definitions.