Skip to content

Conversation

@ggdouglas
Copy link
Contributor

@ggdouglas ggdouglas commented Oct 16, 2025

Proposed Changes

Original draft proposal: #6980

This PR introduces new <Box>, <Flex>, and <Slot> components providing flexible layout utilities for margin, padding, gap, alignment, and positioning.

Components:

  • <Box> is the foundational layout primitive with utility props that generate CSS classes
  • <Flex> is a specialized Box component pre-configured with display="flex"
  • <Slot> is used by Box for polymorphic composition via asChild

Screenshot 2025-10-16 at 16 31 23@2x

Examples

Arranging multiple Buttons horizontally:

<Box display="flex" gap={2}>
  <Button intent="primary">Button</Button>
  <Button intent="success">Button</Button>
  <Button intent="warning">Button</Button>
  <Button intent="danger">Button</Button>
</Box>

CleanShot 2024-09-17 at 14 42 02@2x

Or stacked vertically:

<Box display="flex" flexDirection="column" alignItems="center" gap={2}>
  <Button intent="primary">Button</Button>
  <Button intent="success">Button</Button>
  <Button intent="warning">Button</Button>
  <Button intent="danger">Button</Button>
</Box>

CleanShot 2024-09-17 at 14 44 20@2x

Creating more molecular components (such as EntityTitle):

<Box display="flex" gap={2}>
    <Icon className={Classes.TEXT_MUTED} icon="shop" />
    Buy groceries on my way home
    <Box asChild={true} marginXStart="auto">
        <Tag intent="danger" minimal={true}>
            Due today
        </Tag>
    </Box>
</Box>

CleanShot 2024-09-17 at 14 56 02@2x

@ggdouglas ggdouglas marked this pull request as ready for review October 16, 2025 18:26
@ggdouglas ggdouglas force-pushed the gdouglas/box-and-flex-components branch from 95b2da7 to 88435ea Compare October 16, 2025 18:38
@svc-palantir-github
Copy link

Add Box demo and update demo-app components

Build artifact links for this commit: documentation | landing | table | demo

This is an automated comment from the deploy-preview CircleCI job.

Copy link
Contributor

@ericjeney ericjeney left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎉 🎉 🎉

I'll let @mm-wang do the full review, but left a few preliminary comments on the things that jumped out 🙂


.#{$ns}-box {
@each $i, $value in $sizes {
&.gap-#{$i} {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we add #{$ns}- on all of the classes in this file to avoid conflicting with classes that may be defined in consuming apps?

I think that nesting it all under .#{$ns}-box is sufficient to avoid us polluting an application's CSS, but doesn't do anything to prevent the app's CSS from polluting Blueprint's components.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, I'll take the point about app CSS potentially polluting these utlity classes under consideration. 🤔

I'm hesitant to add the namespace to all utility classes mainly because of verbosity. Like, we'd end up with something like this:

<div class="bp6-box bp6-flex bp6-gap-2 bp6-padding-4 bp6-margin-2">

That would make DOM inspection pretty cluttered and harder to scan at a glance. On the flip side, the main downside of not adding the prefix is the risk of conflicts with other app-level utility frameworks that could potentially collide with Blueprint's utility classes.

My thinking is that many modern apps already use CSS Modules, CSS-in-JS, or scoped styling approaches, which naturally reduce global namespace pollution. Plus, the .bp6-box parent class already provides some scoping since you have to explicitly apply it to use these utilities. And honestly, users who want comprehensive utility classes are probably already reaching for Tailwind or similar frameworks anyway.

Also worth noting that users primarily interact with <Box> via React props (like <Box gap={2} />), so the actual class names are somewhat of an implementation detail.

Copy link
Member

@invliD invliD Oct 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should definitely keep the prefix here, mainly for the reason Eric mentioned, but also for consistency. All of our CSS classes have the prefix, and we shouldn't just change that here. I don't think the additional prefix causes any issues in practice, because like you mentioned people don't write them directly; they either use the Box component, or the Classes constants.

I don't think we should rely on consumers having certain build environments or using certain libraries that may make not having the prefix a non-issue. We can almost guarantee it by just having the namespace, at basically no cost to us.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Prefix added in 6beb43b

@@ -0,0 +1,59 @@
---
tag: new
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we mark these as experimental in some form initially?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, probably a good idea initially if we're including them in core. Perhaps something like a "beta" or "experimental" tag with a callout at the top of each docs page explaining.

develop...gdouglas/docs-beta-tag


Screenshot 2025-10-17 at 15 01 44@2x

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1, and I do want to figure out the testing strategy for this, which will help determine how we roll it out

@svc-palantir-github
Copy link

Export `FlexProps`

Build artifact links for this commit: documentation | landing | table | demo

This is an automated comment from the deploy-preview CircleCI job.

Copy link
Contributor

@mm-wang mm-wang left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would like to test it out, but wanted to leave these comments here first. I like how this is shaping up though!
I'm curious your thoughts on a Grid layout component as well - I know it doesn't have many properties, but it makes sense to me to have an "alternative" to Flex that has its own set of typical properties.

@@ -0,0 +1,59 @@
---
tag: new
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1, and I do want to figure out the testing strategy for this, which will help determine how we roll it out

/**
* Slot component.
*/
export const Slot = forwardRef<HTMLElement, React.HTMLAttributes<HTMLElement> & { children?: React.ReactNode }>(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question/nit: As far as I understand, Slot is a placeholder for either Box or Flex type of layout component. I see we're not exposing it by exporting, but I would maybe make it a bit clearer in terms of naming - perhaps LayoutPlaceholder or something to make it clear it's not intended to be a component that will eventually be used. Definitely a nit though! I don't feel super strongly about this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The primary reason for this being called Slot is that it mirrors/is inspired by the Radix primitive of the same name. I'm open to calling it something else, but I'm not sure if LayoutPlaceholder really conveys the meaning to me more vs. Slot.


import { Slot } from "../../src/components/slot/slot";

describe("<Slot>", () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Add a test for multiple children inside of Slot throwing an error.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, having trouble writing a test here that's compatible with Karma + RTL. I've tried multiple variations around the following:

it("throws an error when multiple children are provided", () => {
    const consoleErrorStub = sinon.stub(console, "error");
    try {
        expect(() => {
            render(
                <Slot>
                    <button>First</button>
                    <button>Second</button>
                </Slot>,
            );
        }).to.throw(TypeError);
    } finally {
        consoleErrorStub.restore();
    }
});

But I can't seem to prevent the TypeError from being caught by Karma's global error catcher and failing the test:

 <Slot>
    throws an error when multiple children are provided
       1) Error: Uncaught TypeError: Only single element child is allowed in Slot (/var/folders/n1/whj_5mt92t10sltc2xkhhhwx08g4by/T/_karma_webpack_337031/commons.js:306)
...

} from "@blueprintjs/core";
import { Example, type ExampleProps } from "@blueprintjs/docs-theme";

const SPACING_VALUES: Array<BoxProps["padding"]> = [0, 0.5, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: This seems to be used a lot in your examples, should it be an exported constant? I'm not sure - if we add to it/change it in the future, then we run into issues. Would love your thoughts.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm personally ok with leaving a bit of duplication (esp. in examples/docs) for now until we find a reason for Blueprint consumers to ask for/need the exported constant. I can't think of a great reason for including it from the inception of these components, but I could be persuaded later / we could always add it later.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was convinced that we can add this now. Implementation: cf1d575

expect(classes).to.include("gap-2");
});

it("should support all flex-related props", () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Do you want more tests for the other props? And for a nested set of Flex components?

/**
* The range of values for size as a percentage.
*/
type SizeRange = 25 | 50 | 75 | 100;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: From the draft, I'd also wondered - why only quarters here? Why not thirds?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Linking to your original comment: #6980 (comment)

Not opposed to adding more options here, but I'm worried that this potentially doesn't scale well to individual classes. How many fractional values do we want to support?

Widths/heights seem like they're perhaps better suited to dynamic computation/inline styles. Something I've toyed around with, but is not formally implemented in this layout components proposal.

@svc-palantir-github
Copy link

Prefix utility classes with `bp6` namespace

Build artifact links for this commit: documentation | landing | table | demo

This is an automated comment from the deploy-preview CircleCI job.

@svc-palantir-github
Copy link

Revert inclusion of `pt-spacing` CSS variable at root

Build artifact links for this commit: documentation | landing | table | demo

This is an automated comment from the deploy-preview CircleCI job.

@ggdouglas ggdouglas force-pushed the gdouglas/box-and-flex-components branch from 28c66ce to 784a7cb Compare October 21, 2025 16:20
@svc-palantir-github
Copy link

Modify options for align/justify content for closer parity with CSS

Build artifact links for this commit: documentation | landing | table | demo

This is an automated comment from the deploy-preview CircleCI job.

@svc-palantir-github
Copy link

Modify box tests to explicitly test for data attributes

Build artifact links for this commit: documentation | landing | table | demo

This is an automated comment from the deploy-preview CircleCI job.

@svc-palantir-github
Copy link

Expose SpacingRange constant and type

Build artifact links for this commit: documentation | landing | table | demo

This is an automated comment from the deploy-preview CircleCI job.

@svc-palantir-github
Copy link

Revert "Expose SpacingRange constant and type"

Build artifact links for this commit: documentation | landing | table | demo

This is an automated comment from the deploy-preview CircleCI job.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants