Skip to content

Instantly share code, notes, and snippets.

@cheapsteak
Created April 26, 2024 21:32
Show Gist options
  • Save cheapsteak/82f98e0b76c2f656b17ba92da18c2004 to your computer and use it in GitHub Desktop.
Save cheapsteak/82f98e0b76c2f656b17ba92da18c2004 to your computer and use it in GitHub Desktop.

On reusable components API design

Let’s compare the different ways Space Kit’s is implemented vs Chakra’s .

Starting with the base case:

// Chakra
<Modal isOpen={true} onClose={() => {}}>
  <ModalOverlay />
  <ModalContent>
    <ModalHeader>Modal Title</ModalHeader>
    <ModalBody>
      <Lorem count={2} />
    </ModalBody>
    <ModalFooter className="gap-x-4">
      <Button variant="ghost">Cancel</Button>
      <Button>Buy One Seat</Button>
    </ModalFooter>
  </ModalContent>
</Modal>
// Space-Kit
<Modal
  size="medium"
  title="Modal Title"
  primaryAction={<Button>Buy 1 Seat</Button>}
  secondaryAction={<Button variant="ghost">Cancel</Button>}
>
  <Lorem count={2} />
</Modal>
  
  
  
  
  

It might seem like Chakra’s is more complicated and verbose since it requires multiple component segments to do what Space-Kit’s Modal can do with just one.

That’s the trap!

Aside: It’s the same trap that Material UI fell victim to nearly ten years ago with their v0 components. Their v0 Dialog component has title and actions as props on the single <Dialog/> component From v1 onwards, Dialog, DialogTitle, DialogAction, and DialogContent have all become separate components.

“Content Overflow” and the importance of addressable sub-components

Modals occasionally need to account for content that is longer than screen height. Say for example we want that content to be scrollable.

When the body of the modal is its own component, we can apply styling to that component in the same ways as we style any other typical component or DOM element.

// Chakra
-    <ModalBody>
+    <ModalBody className="overflow-y-auto">
      <Lorem count={2} />
    </ModalBody>

When a component encompasses multiple things, if styles are applied, it’s not clear which element the style would end up on. In Space-Kit’s modal, it’s also not possible to directly address the “body” of the modal because the abstraction hid that away. There’s nothing to apply className to.

Space-Kit’s Modal created a specific prop called verticalScrollMode, which can have values of either modal or children

// Space-Kit
<Modal
+  verticalScrollMode="children"
   ...
>

Unlike className="overflow-y-auto", which means the same thing on ModalBody as it does on any other component, and will likely be recognizable by people reading through usage sites of the code, verticalScrollMode="children" was invented uniquely for Modal to work around not being able to address the body of the modal.

*Aside: In the *Atomic Design framework, there’s a concept of “Atoms, Molecules, Organisms, Templates, Pages”. Chakra Modal’s subcomponents are atoms that can be recomposed to form different molecules, whereas Space-Kit Modal is one big complex molecule that cannot be broken down any further.

Addressing sub-components via props vs full components

Space-Kit’s Modal actually does make many of its other sub-components addressable, via props. Instances of this can be found in the base-case’s example code via primaryAction and secondaryAction .

Let’s say we wanted to add additional content to the bottom row next to the buttons.

With Chakra, ModalFooter is just a box, so new elements can be added the same way an item would be added to a div.

// Chakra
<Modal isOpen={true} onClose={() => {}}>
  ...
    <ModalFooter>
+     <span>More info...</span>
      <Button variant="ghost">Cancel</Button>
      <Button>Buy One Seat</Button>
    </ModalFooter>
  ...
</Modal>

With Space-Kit, a new prop bottomLeftText was created to be able to address the new subcomponent.

// Space-Kit
<Modal
  ...
+ bottomLeftText={<span>More info...</span>}
...

Now let’s customize the color of the backdrop.

Guess how ModalOverlay’s background color can be customized without needing to check their docs:

// Chakra
...
-  <ModalOverlay />
+  <ModalOverlay className="bg-blue-100" />
...

Space-Kit requires another single-use prop to allow access to the backdrop via containerAs , where the entire component replacing the container would be passed.

// Space-Kit
<Modal
  ...
+  containerAs={<div className="bg-blue-100" />}

And if you want to add a test-id attribute to the container

// Space-Kit
<Modal
  ...
+  as={<div data-test-id="some-modal" />}

Aside: If you subscribe to the notion that React Components are like functions, then it may follow that component api design should subscribe to advice given for functions to some extent, like not having Too Many Parameters. Or, in a functional programming approach, functions should try to be small and simple, and flexibly compose together.

Squaring up the comparisons again

// Chakra
<Modal isOpen={true} onClose={() => {}}>
  <ModalOverlay className="bg-blue-100" />
  <ModalContent data-test-id="some-modal">
    <ModalHeader>Modal Title</ModalHeader>
    <ModalBody className="overflow-y-auto">
      <Lorem count={2} />
    </ModalBody>
    <ModalFooter className="gap-x-4">
      <span>More info...</span>
      <Button variant="ghost">Cancel</Button>
      <Button>Buy One Seat</Button>
    </ModalFooter>
  </ModalContent>
</Modal>
// Space-Kit
<Modal
  size="medium"
  title="Modal Title"
  primaryAction={<Button>Buy 1 Seat</Button>}
  secondaryAction={<Button variant="ghost">Cancel</Button>}
  verticalScrollMode="children"
  bottomLeftText={<span>More info...</span>}
  containerAs={<div className="bg-blue-100" />}
  as={<div data-test-id="some-modal" />}
>
  <Lorem count={2} />
</Modal>


Granted, Space-Kit’s code in this example is still 2 lines shorter than Chakra’s, but with Chakra you have the familiar and repeatable patterns of components and classNames, with Space-Kit you have a potpourri of single-use props.

If you were charged with building the modal designed below, which would you rather have at your disposal? [Image: image.png]

Further reading/watching:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment