Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/smooth-singers-mate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@microsoft/atlas-css': minor
---

Add container query mixins and tokens. New mixins: `container()` for marking elements as query containers, and `container-320`, `container-480`, `container-640`, `container-800` for mobile-first container size queries. Refactored the timeline component to use the new mixins.
4 changes: 4 additions & 0 deletions css/src/components/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ Although we favor unabbreviated names in our design system, we've made one excep
- extra large - xl
- extra extra large - xl

## Container query token and mixin naming

Container query tokens and mixins use the pixel value in the name rather than t-shirt sizes (e.g., `$container-query-480` and `@include container-480` instead of `$container-query-md` and `@include container-md`). This differs from the t-shirt size convention used elsewhere because container query breakpoints describe the intrinsic width of a component's container, not a relative size within a component's own API. A pixel-named token is immediately unambiguous — `container-480` tells you the exact threshold — whereas `container-md` would require looking up the value, and could be confused with viewport breakpoints that also use t-shirt sizes.

## Example

```scss
Expand Down
3 changes: 2 additions & 1 deletion css/src/components/timeline.scss
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
@use 'sass:math';
@use '../tokens/index.scss' as tokens;
@use '../mixins/index.scss' as mixins;
$timeline-content-font-size: tokens.$font-size-8 !default;
$timeline-timestamp-font-size: tokens.$font-size-9 !default;
$timeline-timestamp-font-weight: tokens.$weight-semilight !default;
Expand Down Expand Up @@ -47,7 +48,7 @@ $timeline-item-badge-transform-rtl: translate(
}
}

@container (min-width: #{tokens.$container-query-md}) {
@include mixins.container-480 {
.timeline-item-template {
display: grid;
width: $timeline-item-template-width;
Expand Down
67 changes: 67 additions & 0 deletions css/src/mixins/container-queries.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
@use 'sass:string';
@use '../tokens/index.scss' as tokens;

/// Sets an element as a container query context.
/// @param {String} $name [null] - Optional container name for targeted queries.
/// @param {String} $type [inline-size] - The container type (inline-size or size).
@mixin container($name: null, $type: inline-size) {
container-type: $type;

@if $name {
container-name: $name;
}
}

// Mobile-first container query mixins

/// @param {String} $name [null] - Optional container name to target.
@mixin container-320($name: null) {
$prefix: '';

@if $name {
$prefix: $name + ' ';
}

@container #{$prefix}(min-width: #{tokens.$container-query-320}) {
@content;
}
}

/// @param {String} $name [null] - Optional container name to target.
@mixin container-480($name: null) {
$prefix: '';

@if $name {
$prefix: $name + ' ';
}

@container #{$prefix}(min-width: #{tokens.$container-query-480}) {
@content;
}
}

/// @param {String} $name [null] - Optional container name to target.
@mixin container-640($name: null) {
$prefix: '';

@if $name {
$prefix: $name + ' ';
}

@container #{$prefix}(min-width: #{tokens.$container-query-640}) {
@content;
}
}

/// @param {String} $name [null] - Optional container name to target.
@mixin container-800($name: null) {
$prefix: '';

@if $name {
$prefix: $name + ' ';
}

@container #{$prefix}(min-width: #{tokens.$container-query-800}) {
@content;
}
}
1 change: 1 addition & 0 deletions css/src/mixins/index.scss
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
@forward './media-queries.scss';
@forward './container-queries.scss';
@forward './code-block.scss';
@forward './colors.scss';
@forward './control.scss';
Expand Down
5 changes: 4 additions & 1 deletion css/src/tokens/containers.scss
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
/**
* @sass-export-section="container"
*/
$container-query-md: 480px !default;
$container-query-320: 320px !default;
$container-query-480: 480px !default;
$container-query-640: 640px !default;
$container-query-800: 800px !default;
//@end-sass-export-section
103 changes: 95 additions & 8 deletions site/src/tokens/breakpoints.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,27 @@
---
title: Breakpoints
description: Atlas CSS breakpoints tokens and media queries tokens
description: Atlas CSS breakpoints tokens, media query mixins, and container query mixins
template: token
token: breakpoints
---

# Breakpoints and media queries

Available media queries:
Atlas provides viewport-based media query mixins for responsive layouts. These follow a mobile-first approach — styles apply from the specified breakpoint and up.

```scss
@use '@microsoft/atlas-css/src/mixins' as mixins;

.my-element {
display: block;

@include mixins.tablet {
display: flex;
}
}
```

Available media query mixins:

```scss
@mixin tablet {
Expand All @@ -31,23 +45,96 @@ Available media queries:
// Orientation

@mixin orientation-portrait {
@media screen and (max-aspect-ratio: 1/1),
screen and (min-resolution: 120dpi) and (max-aspect-ratio: 1/1) {
@media screen and (aspect-ratio <= 1/1),
screen and (resolution >= 120dpi) and (aspect-ratio <= 1/1) {
@content;
}
}

@mixin orientation-landscape {
@media screen and (min-aspect-ratio: 1/1),
screen and (min-resolution: 120dpi) and (min-aspect-ratio: 1/1) {
@media screen and (aspect-ratio >= 1/1),
screen and (resolution >= 120dpi) and (aspect-ratio >= 1/1) {
@content;
}
}

@mixin orientation-square {
@media screen and (aspect-ratio: 1/1),
screen and (min-resolution: 120dpi) and (aspect-ratio: 1/1) {
@media screen and (aspect-ratio <= 1/1),
screen and (resolution >= 120dpi) and (aspect-ratio <= 1/1) {
@content;
}
}
```

## Container queries

Container queries let a component respond to the size of its _parent container_ rather than the viewport. This is useful when the same component can appear in different layout contexts (sidebar, main content, full-width) and should adapt accordingly.

Atlas provides two types of container query mixins: one for marking a parent element as a container, and size-based query mixins for applying styles at specific container widths.

### Marking a container

Use the `container` mixin on the parent element that child elements should respond to.

```scss
@use '@microsoft/atlas-css/src/mixins' as mixins;

.card-grid {
@include mixins.container;
}
```

You can also pass an optional name to target a specific container:

```scss
.card-grid {
@include mixins.container('card-grid');
}
```

### Container query breakpoints

Container query breakpoints use pixel values in their names rather than t-shirt sizes. This makes the exact threshold immediately clear and avoids confusion with the viewport breakpoints, which serve a different purpose.

| Token | Value | Mixin |
| --- | --- | --- |
| `$container-query-320` | 320px | `container-320` |
| `$container-query-480` | 480px | `container-480` |
| `$container-query-640` | 640px | `container-640` |
| `$container-query-800` | 800px | `container-800` |

### Querying a container

Use the container query mixins inside child elements. Like viewport media queries, these are mobile-first — styles apply when the container is _at least_ the specified width.

```scss
@use '@microsoft/atlas-css/src/mixins' as mixins;

.card {
// Stack by default
display: block;

// Side-by-side when the container is >= 480px
@include mixins.container-480 {
display: flex;
}
}
```

To target a named container, pass the name as an argument:

```scss
.card {
@include mixins.container-480('card-grid') {
display: flex;
}
}
```

### When to use container queries vs media queries

| Scenario | Use |
| --- | --- |
| Page-level layout changes (e.g., sidebar collapses) | Media queries (`tablet`, `desktop`) |
| Component adapts to the space it's placed in | Container queries (`container-480`, etc.) |
| Component appears in only one layout context | Either works — media queries are simpler |
Loading