]> git.ipfire.org Git - thirdparty/bootstrap.git/commitdiff
New container queries for CSS grid, add new fullscreen demo maybe
authorMark Otto <markdotto@gmail.com>
Mon, 29 Jun 2026 03:35:26 +0000 (20:35 -0700)
committerMark Otto <markdotto@gmail.com>
Mon, 29 Jun 2026 03:36:42 +0000 (20:36 -0700)
scss/_dialog.scss
scss/layout/_grid.scss
scss/mixins/_grid.scss
site/src/components/shortcodes/ResizableExample.astro
site/src/content/docs/layout/css-grid.mdx
site/src/scss/_component-examples.scss

index ee2b36e4001f3b2ff759849cd483de7e044c9aea..990b6284e6848ae735e3b06297f3bee7b326987e 100644 (file)
@@ -233,6 +233,7 @@ $dialog-sizes: defaults(
     --dialog-width: 100vw;
     --dialog-margin: 0;
     --dialog-border-radius: 0;
+    --dialog-border-width: 0;
 
     width: 100%;
     max-width: none;
index dab87eb7d386d776cb87faa16b07e53ecc07d352..53267e9a0719137558606797847ca0699db3271f 100644 (file)
@@ -1,4 +1,5 @@
 @use "../config" as *;
+@use "breakpoints" as *;
 @use "../mixins/grid" as *;
 
 // mdo-do
       --rows: 1;
       --gap: #{$grid-gutter-x};
 
+      @include set-container();
+
       display: grid;
       grid-template-rows: repeat(var(--rows), 1fr);
       grid-template-columns: repeat(var(--columns), 1fr);
       gap: var(--gap);
-
     }
 
     @include make-cssgrid();
index a25007af8216698c0a84a958ecb3872ea7144039..d9252101954c17e2270c61882340f14940054b86 100644 (file)
   @each $breakpoint in map.keys($breakpoints) {
     $prefix: breakpoint-prefix($breakpoint, $breakpoints);
 
-    @include media-breakpoint-up($breakpoint, $breakpoints) {
+    @include container-breakpoint-up($breakpoint, $breakpoints: $breakpoints) {
       @if $columns > 0 {
         @for $i from 1 through $columns {
           .#{$prefix}g-col-#{$i} {
index 525ebed8d78a9119356220a334c2a9273c9f4255..475f7d8891068fbd215f6618f1df89957c0cac9a 100644 (file)
@@ -30,6 +30,12 @@ interface Props {
    * @default true
    */
   showMarkup?: boolean
+  /**
+   * Whether to show a floating badge indicating the active container query
+   * breakpoint for the resized container.
+   * @default false
+   */
+  showBreakpoint?: boolean
 }
 
 const {
@@ -38,7 +44,8 @@ const {
   className,
   initialWidth = '100%',
   minWidth = '200px',
-  showMarkup = true
+  showMarkup = true,
+  showBreakpoint = false
 } = Astro.props
 
 // Support both class and className props
@@ -69,7 +76,58 @@ const simplifiedMarkup = markup.replace(
       style={`width: ${initialWidth}; min-width: ${minWidth};`}
     >
       <Fragment set:html={previewMarkup} />
+      {
+        showBreakpoint && (
+          <output class="bd-resizable-badge" data-resizable-badge aria-live="polite" />
+        )
+      }
     </div>
   </div>
   {showMarkup && <Code code={simplifiedMarkup} lang="html" nestedInExample={true} />}
 </div>
+
+<script>
+  // Breakpoints mirror `$breakpoints` in scss/_config.scss. Container queries are
+  // evaluated against the content-box inline size of the nearest query container.
+  const breakpoints = [
+    { name: 'xs', min: 0 },
+    { name: 'sm', min: 576 },
+    { name: 'md', min: 768 },
+    { name: 'lg', min: 1024 },
+    { name: 'xl', min: 1280 },
+    { name: '2xl', min: 1536 }
+  ]
+
+  function activeBreakpoint(width: number) {
+    let active = breakpoints[0]
+    for (const bp of breakpoints) {
+      if (width >= bp.min) active = bp
+    }
+    return active
+  }
+
+  document.querySelectorAll<HTMLElement>('[data-resizable-badge]').forEach((badge) => {
+    const container = badge.closest<HTMLElement>('.bd-resizable-container')
+    if (!container) return
+
+    // Measure the actual query container (e.g. `.grid`) when present so the badge
+    // matches what `@container` evaluates; otherwise fall back to the container.
+    const target =
+      container.querySelector<HTMLElement>(':scope > *:not(.bd-resizable-badge)') ?? container
+
+    const update = (width: number) => {
+      const bp = activeBreakpoint(width)
+      badge.dataset.breakpoint = bp.name
+      badge.textContent = `${bp.name} · ${Math.round(width)}px`
+    }
+
+    const observer = new ResizeObserver((entries) => {
+      for (const entry of entries) {
+        const inlineSize = entry.contentBoxSize?.[0]?.inlineSize ?? entry.contentRect.width
+        update(inlineSize)
+      }
+    })
+
+    observer.observe(target, { box: 'content-box' })
+  })
+</script>
index 8a022b886da43ecf910e0202956812399c39cede..29ba98e99189138e9676a5d3ba31c6eb14a4a03f 100644 (file)
@@ -2,6 +2,7 @@
 title: CSS Grid
 description: Learn how to enable, use, and customize our alternate layout system built on CSS Grid with examples and code snippets.
 toc: true
+js: required
 csstricks:
   url: https://css-tricks.com/snippets/css/complete-guide-grid/
   label: CSS Grid Guide
@@ -25,7 +26,7 @@ Bootstrap includes an optional grid system built on CSS Grid, but with a Bootstr
 
 - **Columns and gutter sizes are set via CSS variables.** Set these on the parent `.grid` and customize however you want, inline or in a stylesheet, with `--bs-columns` and `--bs-gap`.
 
-In the future, Bootstrap will likely shift to a hybrid solution as the `gap` property has achieved nearly full browser support for flexbox.
+- **Responsive classes use container queries.** Each `.grid` is a query container (`container-type: inline-size`), so responsive `.g-col-*` and `.g-start-*` classes respond to the width of the `.grid` itself rather than the viewport.
 
 ## Key differences
 
@@ -45,7 +46,7 @@ Compared to the default grid system:
 
 ### Three columns
 
-Three equal-width columns across all viewports and devices can be created by using the `.g-col-4` classes. Add [responsive classes](#responsive) to change the layout by viewport size.
+Three equal-width columns across all container sizes and devices can be created by using the `.g-col-4` classes. Add [responsive classes](#responsive) to change the layout by the `.grid` container’s width.
 
 <Example class="bd-example-cssgrid" code={`<div class="grid text-center">
     <div class="g-col-4">.g-col-4</div>
@@ -61,21 +62,71 @@ Three equal-width columns across all viewports and devices can be created by usi
 
 ### Responsive
 
-Use responsive classes to adjust your layout across viewports. Here we start with two columns on the narrowest viewports, and then grow to three columns on medium viewports and above.
+Use responsive classes to adjust your layout across breakpoints. Here we start with two columns on the narrowest containers, and then grow to three columns on medium containers and above. Drag the example’s handle to resize the `.grid` and watch the columns reflow based on its width rather than the viewport.
 
-<Example class="bd-example-cssgrid" code={`<div class="grid text-center">
+<Callout>
+Responsive `.g-col-*` and `.g-start-*` classes are powered by [container queries](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_containment/Container_queries) rather than media queries. The `.grid` is set as a query container (`container-type: inline-size`), so its grid items respond to the width of the `.grid` itself instead of the viewport. This means a `.grid` placed inside a narrow column will switch layouts based on that column’s width, not the size of the browser window.
+</Callout>
+
+<ResizableExample class="bd-example-cssgrid" showBreakpoint code={`<div class="grid text-center">
     <div class="g-col-6 md:g-col-4">.g-col-6 .md:g-col-4</div>
     <div class="g-col-6 md:g-col-4">.g-col-6 .md:g-col-4</div>
     <div class="g-col-6 md:g-col-4">.g-col-6 .md:g-col-4</div>
   </div>`} />
 
-Compare that to this two column layout at all viewports.
+Compare that to this two column layout at all container sizes.
 
 <Example class="bd-example-cssgrid" code={`<div class="grid text-center">
     <div class="g-col-6">.g-col-6</div>
     <div class="g-col-6">.g-col-6</div>
   </div>`} />
 
+### Playground
+
+The examples above are constrained by the width of this page, so you can only resize them up to the `sm` range before running out of room. Open the fullscreen demo below to drag a set of grids across the full range of breakpoints and watch each layout reflow based on the container's width rather than the viewport.
+
+<button type="button" class="btn-solid theme-primary" data-bs-toggle="dialog" data-bs-target="#gridPlaygroundDialog">
+  Open fullscreen grid demo
+</button>
+
+<dialog class="dialog dialog-fullscreen dialog-scrollable" id="gridPlaygroundDialog">
+  <div class="dialog-header">
+    <h1 class="dialog-title">CSS Grid container queries</h1>
+    <CloseButton dismiss="dialog" />
+  </div>
+  <div class="dialog-body">
+    <p>Drag the handle on the right edge of the example to resize it. The badge shows the active container-query breakpoint, and every grid below responds to the example's width&mdash;not the size of the browser window.</p>
+    <ResizableExample class="bd-example-cssgrid" showBreakpoint showMarkup={false} code={`<div class="grid text-center">
+      <div class="g-col-12 sm:g-col-6 md:g-col-4 lg:g-col-3 xl:g-col-2">cell</div>
+      <div class="g-col-12 sm:g-col-6 md:g-col-4 lg:g-col-3 xl:g-col-2">cell</div>
+      <div class="g-col-12 sm:g-col-6 md:g-col-4 lg:g-col-3 xl:g-col-2">cell</div>
+      <div class="g-col-12 sm:g-col-6 md:g-col-4 lg:g-col-3 xl:g-col-2">cell</div>
+      <div class="g-col-12 sm:g-col-6 md:g-col-4 lg:g-col-3 xl:g-col-2">cell</div>
+      <div class="g-col-12 sm:g-col-6 md:g-col-4 lg:g-col-3 xl:g-col-2">cell</div>
+    </div>
+    <div class="grid text-center">
+      <div class="g-col-6 md:g-col-3">card</div>
+      <div class="g-col-6 md:g-col-3">card</div>
+      <div class="g-col-6 md:g-col-3">card</div>
+      <div class="g-col-6 md:g-col-3">card</div>
+    </div>
+    <div class="grid text-center">
+      <div class="g-col-12 lg:g-col-3">sidebar</div>
+      <div class="g-col-12 lg:g-col-9">main content</div>
+    </div>
+    <div class="grid text-center">
+      <div class="g-col-12 lg:g-col-8">primary</div>
+      <div class="g-col-12 lg:g-col-4">secondary</div>
+    </div>
+    <div class="grid text-center">
+      <div class="g-col-12 md:g-col-6 md:g-start-4">centered with .md:g-start-4</div>
+    </div>`} />
+  </div>
+  <div class="dialog-footer">
+    <button type="button" class="btn-solid theme-secondary" data-bs-dismiss="dialog">Close</button>
+  </div>
+</dialog>
+
 ## Wrapping
 
 Grid items automatically wrap to the next line when there’s no more room horizontally. Note that the `gap` applies to horizontal and vertical gaps between grid items.
index ec3a27b76b8d99e909fbeff172e2d8accbd31aee..040ee2f951e0021e1512a3b6ce85887a2db9c575 100644 (file)
   }
 
   .bd-resizable-container {
+    position: relative;
     max-width: 100%;
     padding: 1rem;
     overflow: hidden;
     @include border-radius(var(--radius-5));
   }
 
+  .bd-resizable-badge {
+    position: absolute;
+    inset-block-start: .5rem;
+    inset-inline-end: .5rem;
+    z-index: 2;
+    padding: .125rem .5rem;
+    font-family: var(--bs-font-mono);
+    font-size: .75rem;
+    line-height: 1.5;
+    color: var(--bs-fg-2);
+    pointer-events: none;
+    background-color: var(--bs-bg-body);
+    border: 1px solid var(--bs-border-color);
+    @include border-radius(var(--radius-3));
+  }
+
   //
   // Code snippets
   //