DEV Community

Cover image for Advanced Layouts with Puck 0.18: Harnessing Grid and Flex Containers
Fede Bonel Tozzi for Puck

Posted on • Edited on • Originally published at measured.co

Advanced Layouts with Puck 0.18: Harnessing Grid and Flex Containers

Puck is the open-source visual editor for React, that you can embed in any application to create the next generation of page builders and no-code products. Give us a star on GitHub! ⭐️


Drag-and-drop page-builders are everywhere, but getting them to work seamlessly across different layouts—especially complex CSS Grid and Flexbox structures—has always been a challenge. With Puck 0.18, that just got a lot easier.

This update introduces a revamped drag-and-drop engine designed to work naturally across any CSS layout. Instead of forcing elements into rigid structures, you now have full control over the styles of DropZones and their children to fit your design needs, and let you do things like this:

1intro.gif

With the new drag-and-drop engine, you can add any CSS layout however you want to your editor—but some patterns can make the process much smoother. That’s why in this post, we’ll go over some common patterns I’ve found useful for adding CSS Grid and Flex support to your editors, along with best practices to help you get the most out of Puck’s new engine.

Before we get started, I’ll assume you have a basic understanding of Puck and how it works. If you’re new here, no worries—you’re welcome to follow along! However, I’d recommend checking out the Getting Started guide first to familiarize yourself with the basics, as well as the 0.18 blog post to learn about the new features in this version.

Table of contents

  1. Grid patterns
    1. Grid container pattern
    2. Grid container - Grid Item pattern
    3. Grid container - Any item pattern
    4. Grid layout pattern
  2. Flex patterns
    1. Flex container pattern
    2. Flex container - Flex item pattern
    3. Flex container - Any item pattern
  3. Best practices
    1. Add dynamic padding for editing
    2. Prevent unwanted layout shifts
    3. Give hints with collisionAxis

🍱 Grid patterns

CSS Grids are built for structured, multi-column layouts with precise control over item placement. Unlike Flexbox, which distributes items dynamically based on content size, Grids let you define fixed or flexible tracks, making them a great fit for dashboards, forms, bento layouts, and any design where the number of rows and columns matters.

With Puck’s drag-and-drop engine, you can give users full control over CSS rules so they can build grids however they like. But in most cases, you'll want to guide them through the process to keep things intuitive for all levels of experience. Sometimes that means letting them define the number of columns and rows in a grid freely; other times, it makes more sense to provide a set of predefined grid designs from which to choose from.

With this in mind, in this section I’ve gathered four patterns I’ve found especially useful for adding grids to build intuitive and flexible page-building experiences across a variety of use cases.

Grid container pattern

The Grid container pattern is the foundation of all grid-based layouts in Puck. With this approach, the grid DropZone itself defines the grid layout, so that all elements inside follow a predefined number of rows or columns. This is useful when you want to give control over the layout container and need a regular arrangement of elements in columns and rows, so it’s ideal for things like lists and content grids.

To set it up you need to follow these steps:

1. Define a new component in your Puck config for the grid

const config = {
  components: {   
    // Define your Grid component for grid layouts
    Grid: {
      //... next steps here
    },
  },
};
Enter fullscreen mode Exit fullscreen mode

2. Optionally, add fields to let users configure the grid—for example, you could add fields to set rules like the number of columns and rows

const config = {
  components: {
    Grid: {
      // Add fields to configure the numbers of rows/columns in the grid
      fields: {
        columns: { type: "number" },
        rows: { type: "number" },
      },
    },
  },
};
Enter fullscreen mode Exit fullscreen mode

3. Finally, render a DropZone inside the Grid component, applying a display: grid CSS rule and any other desired layout rules or user-defined settings

const config = {
  components: {
    Grid: {
      //... existing config

      // Render the DropZone as a CSS grid
      render: ({ columns, rows }) => {
        return (
          <DropZone
            zone="grid-zone"
            style={{
                display: "grid",
              gridTemplateColumns: `repeat(${columns || 1}, 1fr)`,
              gridTemplateRows: `repeat(${rows || 1}, auto)`,
            }}
          />
        );
      },
    },
  },
};
Enter fullscreen mode Exit fullscreen mode

If you followed these steps, you’ll now be able to navigate to the editor in the browser, drag and drop a Grid, set the number of columns and rows for it, and drop items inside to automatically snap them to the grid structure:

grid-container.gif

As you can see, this pattern works great for structured layouts, but if you need more flexibility—like controlling how individual items are placed within the grid, or allow the user to define custom multi-column layouts—you should check out the Grid container - Grid item pattern, which we’ll cover next.

Grid container - Grid item pattern

The Grid container - Grid item pattern builds on the Grid container pattern by giving you more flexibility in how items are arranged. Instead of enforcing a uniform layout where every item spans the same number of rows and columns, this approach lets users break grids into distinct sections with their own configurable drop zones.

The key to achieve this is adding a GridItem component to your config. This component should render a DropZone, provide fields to set how many rows and columns that DropZone spans in the grid, and be restricted to use only inside Grid components. With that in place you would allow users to:

  • Drag and drop Grid Item zones inside a Grid
  • Set how many columns and rows each zone should occupy
  • Organize the layout visually by dragging and dropping zones around
  • Drag and drop content into each zone

That’s why, with this pattern, you can define custom layouts directly in the editor. For example, you could create a layout with a navbar zone that spans all columns at the top, a table of contents that spans all rows on the left, and a main content section on the right.

To implement this pattern, follow these steps:

1. Add a Grid component to your Puck config

const config = {
  components: {
    //... existing config

    // Define a Grid component
    Grid: {
      render: () => {
        return (
          <DropZone
            zone="grid-zone"
            style={{
              display: "grid",
              gridTemplateColumns: `repeat(3, 1fr)`,
            }}
          />
        );
      },
    },
  },
};
Enter fullscreen mode Exit fullscreen mode

2. Assign the allow prop on the Grid DropZone to an array containing only the name of your GridItem component. This will only allow GridItems inside the grid

const config = {
  components: {
    Grid: {
      render: () => {
        return (
          <DropZone
            //... existing config

            // Only allow grid items inside the grid
            allow={["GridItem"]}
          />
        );
      },
    },
  },
};
Enter fullscreen mode Exit fullscreen mode

3. Add the GridItem component to your Puck config

const config = {
  components: {
    //... existing config

    // Define your Grid Item component
    GridItem: {
      //... next steps here
    },
  },
};
Enter fullscreen mode Exit fullscreen mode

4. Remove the default wrapping element Puck adds around draggable components for the GridItem by enabling the inline parameter. This will remove the Puck wrapper and allow you to style the grid items as direct children of the grid they are dropped in

const config = {
  components: {
    //... existing config

    GridItem: {
      // Remove the default element wrapper
      inline: true,
    },
  },
};
Enter fullscreen mode Exit fullscreen mode

5. Add fields to the GridItem component to define the number of columns and rows each item should span

const config = {
  components: {
    GridItem: {
      //... existing config

      // Add fields for the number of columns/rows the item should span
      fields: {
        columns: { type: "number" },
        rows: { type: "number" },
      },
    },
  },
};
Enter fullscreen mode Exit fullscreen mode

6. Render the DropZone inside the GridItem with the number of columns and rows it should span. Also, since you previously enabled the inline parameter, you must pass the puck.dragRef to the DropZone to let Puck know it should be treated as a draggable element

const config = {
  components: {
    GridItem: {
      //... existing config

      // Render the DropZone with the number of columns/rows it should span
      render: ({ columns, rows, puck }) => {
        return (
          <DropZone
            ref={puck.dragRef}
            zone="grid-item-zone"
            style={{
              gridColumn: `span ${columns || 1}`,
              gridRow: `span ${rows || 1}`,
            }}
          />
        );
      },
    },
  },
};
Enter fullscreen mode Exit fullscreen mode

7. Finally, disallow GridItems to be nested inside other GridItems to provide a better experience while dragging elements around grids

const config = {
  components: {
    GridItem: {
      render: ({ columns, rows, puck }) => {
        return (
          <DropZone
            //... existing config

            // Disallow GridItems inside
            disallow={["GridItem"]}
          />
        );
      },
    },
  },
};
Enter fullscreen mode Exit fullscreen mode

After following these steps, you should now be able to navigate to the browser, drag and drop a Grid, add GridItems inside, set the number of columns and rows each item should span, and drag any component inside these grid zones to automatically adjust it to fit the layout.

grid-container-grid-item.gif

This pattern is great when you want to provide full control over layout structure, but if you just need a way for users to drop any component into a grid and still define how much space it should take up inside, the Grid container - Any item pattern is a better fit.

Grid container - Any item pattern

The Grid container - Any item pattern is a more flexible approach to working with grids, allowing any component to be placed inside while still letting users control how much space each item takes up. Instead of limiting grid items to a specific type, this pattern enables any component to define their own grid placement dynamically when dropped inside of a grid.

With this pattern, users can:

  • Drop any component inside a grid
  • Define how many rows and columns each item should span
  • Keep components fully functional outside of grids by hiding the rows and columns fields

The key here is using dynamic fields to adjust component settings based on their parent. If a component is inside a Grid, it should show columns and rows fields to let users define its span. If it’s outside, those fields should stay hidden.

To implement this pattern, follow these steps:

1. Add a Grid component to your Puck config

const config = {
  components: {
    //... existing config

    // Define a Grid component
    Grid: {
      render: () => {
        return (
          <DropZone
            zone="grid-zone"
            style={{
              display: "grid",
              gridTemplateColumns: `repeat(3, 1fr)`,
            }}
          />
        );
      },
    },
  },
};
Enter fullscreen mode Exit fullscreen mode

2. Use the resolveFields API on any component to dynamically show the columns and rows fields when placed inside a Grid parent, hiding them when the component is used outside a grid

const config = {
  components: {
    //... existing setup

    AnyComponent: {
      // Remove the default element wrapper
      inline: true,

      // Use dynamic fields to show "Columns" and "Rows" only when inside a Grid
      resolveFields: (data, { parent }) => {
        let fields = {
          //... existing fields
        };

        if (parent?.type === "Grid") {
          fields = {
            ...fields,
            columns: { type: "number" },
            rows: { type: "number" },
          };
        }

        return fields;
      },

      // Render the component with the correct grid placement
      render: ({ columns, rows, puck }) => {
        return (
          <div
            ref={puck.dragRef}
            style={{
              gridColumn: `span ${columns || 1}`,
              gridRow: `span ${rows || 1}`,
            }}
          >
            Any Component
          </div>
        );
      },
    },
  },
};
Enter fullscreen mode Exit fullscreen mode

PRO TIP: If you find yourself repeating this setup multiple times for any components you wish to drop inside a grid, consider creating a withGrid(componentConfig) function that adds the resolveFields setup above to avoid code repetition

After setting this up, you can navigate to the editor, drop any component into any grid, adjust its column and row span, and see the layout update in real time. If the component is moved outside of the grid, those fields will disappear, keeping it adaptable to different layouts.

grid-container-any-item.gif

This pattern works great if you want to allow any item inside a grid while still controlling how each item is positioned. However, there are times when you might want to restrict users to a specific set of predefined grid layouts so they don’t have to worry about design details. For those cases, the Grid layout pattern is the best option.

Grid layout pattern

The Grid layout pattern allows users to choose from predefined grid layouts instead of manually adjusting each item's placement or setting up the layout themselves. This is useful when you want to streamline the page-building process and ensure design consistency while still offering flexibility.

The key difference from the Grid container - Any item and Grid container - Grid item patterns is that here, the grid itself determines how components are arranged based on multiple pre-implemented designs, rather than the nested components defining their own placement based on user input.

With this pattern, users can:

  • Select a grid design from a predefined set of grid layouts
  • Drop any components into the grids
  • Maintain a clean and predictable design without manual configuration

To implement this pattern, follow these steps:

1. Define a Grid component with a layout select field to allow users to choose a predefined grid design. Each option’s value should be the CSS class that implements that specific design

// Import designs
import "./Editor.css";

const config = {
  components: {
    Grid: {
      fields: {
        // Add a layout select input to choose a certain grid design
        layout: {
          type: "select",
          options: [
            { label: "Hero", value: "hero-layout" },
            { label: "Content List", value: "content-list-layout" },
          ],
        },
      },
      // Read the layout design to style the dropzone based on it
      render: ({ layout }) => {
        return (
          <DropZone zone="grid-zone" className={`grid ${layout || ""}`} />
        );
      },
    },
  },
};
Enter fullscreen mode Exit fullscreen mode

2. Define the CSS classes for your grid layouts so that each option applies a different design to the grid

/* ./Editor.css */

/* Base grid styles */
.grid {
  display: grid;
}

/* Hero section layout */
.hero-layout {
  grid-template-columns: 1fr 1fr;
  gap: 16px;
}

/* Layout for a list of content */
.content-list-layout {
  grid-template-columns: repeat(4, 1fr);
  gap: 8px;
}
Enter fullscreen mode Exit fullscreen mode

Once set up, users can drop components into a grid without worrying about manual positioning. They’ll be able to select a layout design from the list of options you provide and the grid items will automatically adjust to match the chosen design.

grid-layout.gif

This is ideal for use cases where structure and consistency are important, like root-level page layouts, forms, or articles.

↩️ Flex patterns

Flexbox is all about dynamic, content-aware layouts. Unlike CSS Grids, which are great for structured designs with fixed tracks, Flexbox shines when you need elements to adjust based on their content and available space. It’s perfect for things like responsive lists and card layouts where items should naturally grow, shrink, or wrap as needed.

Once again, with Puck, you could give users full control over Flexbox CSS rules so that they can fine-tune every detail on their pages. But most of the time, when creating a page builder, you’ll want to abstract away from those complexities so that anyone can create pages without worrying about layout intricacies. After all, your user won’t always be a developer, and Flexboxes are famous for tripping over even the most seasoned front-end engineer from time to time.

That’s why, in this section, I’m sharing three patterns you can use to build intuitive, flexible page-building experiences with Flexbox in Puck.

Flex container pattern

The Flex container pattern is the simplest way to structure a layout using Flexbox in Puck, and it’s the basis of all other flex patterns. In this approach, the DropZone itself defines the flex behavior, so all elements inside follow the container’s layout rules. This works well when you want a flexible, row-based or column-based structure where items automatically adjust based on their content and available space. It’s especially useful for things like horizontal navigation bars, stacked sections, or dynamic lists.

To set it up, follow these steps:

1. Define a new component in your Puck config for the Flex container

const config = {
  components: {
    // Define your flex container
    FlexContainer: {
      //... next steps here
    },
  },
};
Enter fullscreen mode Exit fullscreen mode

2. Optionally, add fields to let users configure the flex behavior—for example, you might allow them to control flex-direction

const config = {
  components: {
    FlexContainer: {
      // Add fields to configure flex behavior
      fields: {
        direction: {
          type: "select",
          options: [
            { label: "Row", value: "row" },
            { label: "Column", value: "column" },
          ],
        },
      },
    },
  },
};
Enter fullscreen mode Exit fullscreen mode

3. Render a DropZone inside the Flex container, applying display: flex and any user-defined settings

const config = {
  components: {
    FlexContainer: {
      //... existing config

      // Render the DropZone as a flex container with user-defined settings
      render: ({ direction = "row" }) => {
        return (
          <DropZone
            zone="flex-zone"
            style={{
              display: "flex",
              flexWrap: "wrap",
              flexDirection: direction,
            }}
          />
        );
      },
    },
  },
};
Enter fullscreen mode Exit fullscreen mode

If you followed these steps, you should now be able to navigate to the editor, drop a Flexbox component, add some items inside, modify the direction of the container, and see the elements automatically arrange according to the user’s settings:

flex-container.gif

This pattern is great for simple, responsive layouts where all items flow naturally within a flexible container. If you need more control over individual item behavior—such as setting different flex-grow or alignment rules per item—you’ll want to explore the Flex container - Flex item pattern next.

Flex container - Flex item pattern

The Flex container - Flex item pattern is a simple yet powerful way to structure layouts using Flexbox. With it, you define a Flex container that holds Flex items where each item acts as a section where users can drop other components. This makes it useful for setting up flexible page structures—like a sidebar and main content area—or components that need to adapt to different screen sizes, such as a card with three horizontal sections that wrap when space is limited.

The key to this pattern is introducing a Flex Item component that wraps each child inside the Flex container. This component, then, provides controls for flex-grow, flex-shrink, and flex-basis, allowing users to tweak layout behavior visually inside the editor. With that in place you would allow users to:

  • Drag and drop Flex Item zones inside a Flex container
  • Control how each zone should grow, shrink, and take up space
  • Organize the layout visually by dragging and dropping zones around
  • Drag and drop content into each zone

To implement this pattern, follow these steps:

1. Define a new component in your Puck config for the Flex container

const config = {
  components: {
    // Define a Flex container component
    FlexContainer: {
      render: () => {
        return (
          <DropZone
            zone="flex-zone"
            style={{
              display: "flex",
              flexWrap: "wrap"
            }}
          />
        );
      },
    },
  },
};
Enter fullscreen mode Exit fullscreen mode

2. Restrict the container to only allow FlexItems inside

const config = {
  components: {
    FlexContainer: {
      render: () => {
        return (
          <DropZone
            zone="flex-zone"
            // Only allow FlexItem components inside
            allow={["FlexItem"]}
            style={{
              display: "flex",
              flexWrap: "wrap"
            }}
          />
        );
      },
    },
  },
};
Enter fullscreen mode Exit fullscreen mode

3. Define the FlexItem component

const config = {
  components: {
    FlexItem: {
      // Remove the default wrapper around the component to style it as a child
      // of a flexbox
      inline: true,
    },
  },
};
Enter fullscreen mode Exit fullscreen mode

4. Add fields for controlling the flex properties of the FlexItems

const config = {
  components: {
    FlexItem: {
      fields: {
        grow: { type: "number" },
        shrink: { type: "number" },
        basis: { type: "text" },
      },
    },
  },
};
Enter fullscreen mode Exit fullscreen mode

5. Render the DropZone inside the FlexItem, applying any required flex properties

const config = {
  components: {
    FlexItem: {
      render: ({ grow, shrink, basis, puck }) => {
        return (
          <DropZone
            ref={puck.dragRef}
            zone="flex-item-zone"
            style={{
              flexGrow: grow ?? 0,
              flexShrink: shrink ?? 1,
              flexBasis: basis ?? "auto",
            }}
          />
        );
      },
    },
  },
};
Enter fullscreen mode Exit fullscreen mode

6. Finally, prevent nesting Flex Items inside each other

const config = {
  components: {
    FlexItem: {
      render: ({ grow, shrink, basis, puck }) => {
        return (
          <DropZone
            disallow={["FlexItem"]} // Prevents nesting
            ref={puck.dragRef}
            zone="flex-item-zone"
            style={{
              flexGrow: grow,
              flexShrink: shrink,
              flexBasis: basis,
            }}
          />
        );
      },
    },
  },
};
Enter fullscreen mode Exit fullscreen mode

Once set up, you’ll be able to drop a FlexContainer onto the page, add FlexItems inside, and adjust their behavior using the flex properties. You can then place any other components inside these sections, letting them adapt naturally within the layout.

flex-container-flex-item.gif

This pattern is ideal when you need a flexible page layout, but if you’re looking for a way to add any component inside a flex container and still be able control it’s flex properties dynamically, the Flex container - Any item pattern might be a better fit.

Flex container - Any item pattern

The Flex container - Any item pattern is a more adaptable approach to using flex layouts, allowing any component to be placed inside while giving users control over how each item behaves within the flex container.

With this pattern, users can:

  • Drop any component inside a flex container
  • Control how items grow, shrink, and take up space
  • Keep components fully functional outside of flex containers by hiding flex-specific controls

The key here is using dynamic fields to expose flex properties like flex-grow, flex-shrink, and flex-basis only when a component is inside a Flex container. Outside of it, these fields should remain hidden to avoid unnecessary complexity.

To implement this pattern, follow these steps:

1. Define a new component in your Puck config for the Flex container

const config = {
  components: {
    // Define a Flex container component
    FlexContainer: {
      render: () => {
        return (
          <DropZone
            zone="flex-zone"
            style={{
              display: "flex",
              flexWrap: "wrap",
            }}
          />
        );
      },
    },
  },
};
Enter fullscreen mode Exit fullscreen mode

2. Define any component that can be placed inside a flex container, dynamically adjusting its flex related fields based on the parent container

const config = {
  components: {
    //... existing setup

    AnyComponent: {
      // Remove the default element wrapper
      inline: true,

      // Use dynamic fields to show "Flex Grow," "Flex Shrink," and "Flex Basis" 
      // only when inside a FlexContainer
      resolveFields: (data, { parent }) => {
        let fields = {
          //... existing fields
        };

        if (parent?.type === "FlexContainer") {
          fields = {
            ...fields,
            flexGrow: { type: "number" },
            flexShrink: { type: "number" },
            flexBasis: { type: "text" },
          };
        }

        return fields;
      },

      // Render the component with the configured flex behavior
      render: ({ flexGrow, flexShrink, flexBasis, puck }) => {
        return (
          <div
            ref={puck.dragRef}
            style={{
              flexGrow: flexGrow ?? 1,
              flexShrink: flexShrink ?? 1,
              flexBasis: flexBasis ?? "auto",
            }}
          >
            Any Component
          </div>
        );
      },
    },
  },
};
Enter fullscreen mode Exit fullscreen mode

PRO TIP: If you plan to use this setup for multiple components, consider creating a withFlex(componentConfig) function that adds the resolveFields setup above to avoid code repetition

Once set up, users can drop any component into a flex container, tweak its growth and shrink behavior, and see changes in real-time. If the component is moved outside of the flex container, those controls will disappear, keeping it adaptable for different layouts.

flex-container-any-item.gif

This pattern is great for flexible, dynamic layouts where elements need to adjust their sizes based on available space.

🔷 Best practices

Beyond the patterns we’ve covered for integrating CSS Grid and Flexbox, there are a few best practices or tips that can help improve the overall drag-and-drop experience and make your editor feel smoother and more intuitive.

Add dynamic padding for editing

When working with nested layouts in the editor, selecting the right component with your cursor can get tricky—especially if DropZones are tightly packed. A simple way to improve usability is by adding space around DropZones only when the page is being edited. To do this, you can use the puck.isEditing render prop, to add temporarily padding around DropZones, making it easier to grab and move entire groups of nested components without accidentally selecting inner ones:

const config = {
  components: {
    NestedComponent: {
      render: ({ puck }) => {
        return (
          <DropZone
            zone="dropzone-with-padding"
            // If rendering in the editor, add padding, otherwise don't add any
            styles={{ padding: puck.isEditing ? "16px" : undefined }}
          />
        );
      },
    },
  },
};
Enter fullscreen mode Exit fullscreen mode

Prevent unwanted layout shifts

Nothing disrupts a drag-and-drop experience more than unexpected layout shifts while dragging elements around. To keep things predictable, you can use the allow and disallow props on your DropZones to control where components can be placed, that way, you can prevent seeing whole lists or containers move elements around when you just wanted to move a page header to the top of your canvas:

const config = {
  components: {
    Header: {
      render: () => {
        return <h1>My header</h1>;
      },
    },
    ProductsList: {
      render: ({ puck }) => {
        return (
          <DropZone
            zone="product-zone"
            // Only allow product items in this zone
            allow={["ProductItem"]}
          />
        );
      },
    },
    ProductItem: {
      render: () => {
        return <div>Product</div>;
      },
    },
  },
};
Enter fullscreen mode Exit fullscreen mode

We used this in the Grid container - Grid item and Flex container - Flex item patterns to avoid nesting of sections and provide a better experience when moving them around the container.

Give hints with collisionAxis

Use the DropZone collisionAxis prop to give Puck a hint about which direction your components flow.

By default, Puck will automatically identify the most likely drag direction based on your DropZone’s CSS properties, but sometimes you might have particular behavior that Puck can’t interpret. For example, if you have a flex container that doesn’t wrap, setting an x (horizontal) collision axis will help it to know where it should try to add new elements:

const config = {
  components: {
    Example: {
      render: () => {
        return (
          <DropZone
            zone="my-content"
            collisionAxis="x"
            style={{ display: "flex", flexWrap: "noWrap" }}
          />
        );
      },
    },
  },
};
Enter fullscreen mode Exit fullscreen mode

Ready to start building with Puck?

In this article, we covered some key patterns and best practices to help you build intuitive, flexible visual editors with Puck and take advantage of all the new features v0.18 introduces. Whether you're working with grids or flex layouts, these techniques should give you a solid foundation to create great editing experiences.

We’d love to hear what you're building! If you have questions, want to suggest improvements, contribute, or just bounce around ideas, here’s how you can get involved:

🌟 Star Puck on GitHub to show support and help others discover it.

🤝 Join our Discord to chat with the community and share your work.

📢 Follow Puck on X and Bluesky for updates, sneak peeks, and tips.

Have thoughts or feedback? Drop a comment—we’re always up for a good discussion.

Happy building! 🚀

Top comments (0)