Agent Skills › getsentry/sentry › migrate-container-queries

migrate-container-queries

GitHub

提供Sentry前端从视口媒体查询迁移到容器查询的指南,指导替换响应式逻辑、重构组件及验证视觉效果。

.agents/skills/migrate-container-queries/SKILL.md getsentry/sentry

Trigger Scenarios

将@media或useMedia迁移为容器查询 重构响应式组件以使用Container/Flex/Grid原语 处理DE容器查询迁移任务

Install

npx skills add getsentry/sentry --skill migrate-container-queries -g -y
More Options

Non-standard path

npx skills add https://github.com/getsentry/sentry/tree/master/.agents/skills/migrate-container-queries -g -y

Use without installing

npx skills use getsentry/sentry@migrate-container-queries

指定 Agent (Claude Code)

npx skills add getsentry/sentry --skill migrate-container-queries -a claude-code -g -y

安装 repo 全部 skill

npx skills add getsentry/sentry --all -g -y

预览 repo 内 skill

npx skills add getsentry/sentry --list

SKILL.md

Frontmatter
{
    "name": "migrate-container-queries",
    "description": "Guide for migrating viewport media queries (@media, useMedia, and screen:-prefixed responsive props) to container queries in Sentry's frontend. Use when migrating responsive layout to container queries, replacing @media\/useMedia\/screen: breakpoints, refactoring styled responsive components to Container\/Flex\/Grid primitives, or working on the DE container-query migration."
}

Container Query Migration Guide

Migrate viewport-based responsive logic (@media, useMedia, and screen:-prefixed responsive props) to container queries so components respond to their own available space instead of the raw viewport.

Always do a visual check. After every migration, resize the element (not just the window) and confirm the layout is identical and flips at the intended width. A good way to narrow an element without touching the window is to open a resizable panel next to it — e.g. drag out the Seer explorer sidebar, which squeezes the middle content. The token scales differ, so a mechanical swap that compiles can still render wrong.

Approach: refactor first, swap second

Stop at the first rung that fits. Prefer replacing hand-rolled CSS with primitives over a mechanical token swap.

Rung When Do
1. Primitive props The @media only flips layout (flex-direction, display, grid-template, gap, visibility, width) Delete the styled component; use Container/Flex/Grid/Stack responsive props (direction={{xs: 'column', md: 'row'}})
2. @container swap CSS can't be a prop (descendant selectors, pseudo-elements, font-size, complex grid-template-areas) Keep the styled component; swap @media → @container, theme.breakpoints.* → theme.container.*
3. Container-scoped JS Width is read in JS to branch rendering Replace useMedia(...) with useResponsivePropValue({...}) for a threshold boolean, or useContainerBreakpoint() to branch on the active key
4. Leave as useMedia Genuine media feature, not width Do nothing — these do not migrate

⚠️ Convert to the nearest container scale

Breakpoint and container scales have different keys and different pixel values — this is not a rename. MAP BY PIXEL VALUE, NOT BY KEY: breakpoints.sm does NOT become container.sm. Reusing the same key is the #1 migration bug.

theme.breakpoints (viewport / @media), base 2xs:

2xs xs sm md lg xl 2xl
0px 500px 800px 992px 1200px 1440px 2560px

theme.container (container / @container), base zero:

zero 3xs 2xs xs sm md lg xl 2xl 3xl 4xl 5xl
0px 320px 384px 448px 512px 576px 640px 768px 896px 1024px 1152px 1280px

Rule: take the old breakpoint's pixel value and pick the container token whose pixel value is nearest to it — not the token with the same name. breakpoints.sm is 800px, so it maps to container.xl (768px), not container.sm (512px). Then confirm with a visual check: the container is often narrower than the viewport, so the nearest-px token is a starting point, not a guarantee.

Genuine viewport width → screen: keys, not useMedia

When layout truly must follow the window (not the component's room), don't keep useMedia — use a screen:-prefixed responsive prop, which resolves against the viewport on the theme.breakpoints scale: direction={{zero: 'column', 'screen:lg': 'row'}}. Bare keys and screen: keys can mix on one prop. Prefer bare (container) keys; reach for screen: only when the viewport genuinely drives the layout.

Existing screen: props are migration candidates

screen:-prefixed responsive props compile to viewport media queries. Audit them in the same pass as @media and useMedia. If the layout responds to the component's available width, remove the screen: prefix and use bare container keys. Map the old viewport breakpoint by pixel value, and add an explicit container base when the narrow layout differs:

// Old — viewport width drives a component layout
<Grid columns={{'screen:2xs': '1fr', 'screen:sm': 'auto 1fr auto auto'}} />

// New — component width drives the layout; screen sm (800px) → container xl (768px)
<Grid columns={{zero: '1fr', xl: 'auto 1fr auto auto'}} />

Keep the screen: key only when the window itself is the intended source of truth. screen: is not a container-query migration target just because it is already a responsive prop.

Keep useMedia only for non-width media features

Width — container or viewport — has a prop/hook path above. Leave useMedia in place only for: prefers-color-scheme, prefers-reduced-motion, hover, pointer, max-height / height-based, resolution, print.

container-type: only when no query container is in scope

Default: don't add one. Bare keys and @container already resolve against the nearest ancestor container, and product views have one: ContentStack (#main, views/organizationLayout/index.tsx) wraps the routed <Outlet /> with containerType="inline-size"; topBar and #modal-portal cover their own subtrees. Add container-type only when a subtree must respond to its own width rather than the page's — then:

  • Use inline-size (width only). size also queries height, which collapses content unless height is set elsewhere.
  • In a reusable component that may already sit inside a container, make it conditional to avoid a redundant one — containerType={hasParentQueryContainer ? 'normal' : 'inline-size'} via useHasContainerQuery() (see components/core/breadcrumbList/breadcrumbList.tsx).

Examples

Rung 1 — styled @media → primitive props (preferred)

// Old — delete the styled component
const Row = styled('div')`
  display: flex;
  flex-direction: row;
  gap: ${p => p.theme.space.md};
  @media (max-width: ${p => p.theme.breakpoints.sm}) {
    flex-direction: column;
  }
`;

// New
import {Flex} from '@sentry/scraps/layout';
<Flex direction={{xs: 'column', sm: 'row'}} gap="md">

Rung 2 — @media → @container (when it can't be a prop)

// Old
@media (max-width: ${p => p.theme.breakpoints.md}) { ... }

// New — swap at-rule AND scale; md breakpoint (992px) → nearest container token by px
// is 3xl (1024px), NOT theme.container.md by matching key
@container (max-width: ${p => p.theme.container['3xl']}) { ... }

Rung 3 — useMedia (width) → container-scoped JS

Both helpers below read the nearest query container (call from a descendant of one) and re-render as it crosses a breakpoint. A single max-width boolean is cleanest as a responsive value; reach for the active key only when you branch on the key itself.

// Old
const isNarrow = useMedia(`(max-width: ${theme.breakpoints.sm})`);

// New — resolve a responsive boolean against the container, same mobile-first
// cascade as CSS. A max-width query is "on by default, off past the threshold",
// so name only the threshold key. Map by pixel value: breakpoints.sm (800px) →
// nearest container token is xl (768px).
import {useResponsivePropValue} from '@sentry/scraps/layout';

const isNarrow = useResponsivePropValue({zero: true, xl: false});
// below xl → true, at/above xl → false — one key on each side, nothing to enumerate.

Reach for useContainerBreakpoint() instead only when you branch on the key itself (e.g. picking one of several layouts), not a single threshold. It returns the container's active key ('zero' … '5xl') — don't compare it with === 'zero' for a max-width case: that fires only below 320px and drops the 320–768px range the original query treated as narrow.

Migration Checklist

Took the lowest rung that fits (above). Then verify the gotchas:

  • Mapped to the container token with the nearest pixel value, not the same name — e.g. breakpoints.sm → container.xl, not container.sm
  • For width read in JS, used useResponsivePropValue({...}) for a threshold boolean; reserved useContainerBreakpoint() for branching on the key — never === 'zero' to mean "narrow" (that's only <320px)
  • Routed genuine viewport-width cases to screen: keys; kept useMedia only for non-width media features
  • Audited existing screen:-prefixed layout props — migrated component-width cases to bare container keys and kept screen: only for genuine viewport-width behavior
  • Added container-type only when a subtree needs its own; used inline-size
  • Confirmed a query-container ancestor exists (@container silently no-ops without one)
  • Visual check: resized the element and confirmed identical output flipping at the intended width

Version History

  • 946f25b Current 2026-08-29 07:37

    新增对 screen: 前缀响应式属性的迁移引用

  • d3c9056 2026-08-20 20:33

Same Skill Collection

.agents/skills/bump-sentry-dependency/SKILL.md
.agents/skills/cmdk-actions/SKILL.md
.agents/skills/design-system/SKILL.md
.agents/skills/feature-flags/SKILL.md
.agents/skills/frontend-data-fetching/SKILL.md
.agents/skills/generate-frontend-forms/SKILL.md
.agents/skills/generate-migration/SKILL.md
.agents/skills/generate-snapshot-tests/SKILL.md
.agents/skills/hybrid-cloud-rpc/SKILL.md
.agents/skills/hybrid-cloud-test-gen/SKILL.md
.agents/skills/lint-fix/SKILL.md
.agents/skills/lint-new/SKILL.md
.agents/skills/migrate-frontend-forms/SKILL.md
.agents/skills/notification-platform/SKILL.md
.agents/skills/react-component-documentation/SKILL.md
.agents/skills/react-testing/SKILL.md
.agents/skills/scraps-review/SKILL.md
.agents/skills/seer-embed/SKILL.md
.agents/skills/sentry-backend-bugs/SKILL.md
.agents/skills/sentry-javascript-bugs/SKILL.md
.agents/skills/sentry-security/SKILL.md
.agents/skills/analytics/SKILL.md
.agents/skills/backend-conventions/SKILL.md
.agents/skills/cell-architecture/SKILL.md
.agents/skills/django-models/SKILL.md
.agents/skills/hybrid-cloud-outboxes/SKILL.md
.agents/skills/migrate-breadcrumb-list/SKILL.md
.agents/skills/remove-option-or-flag/SKILL.md
.agents/skills/setup-dev/SKILL.md

Metadata

Files
0
Version
991ee88
Hash
baac301d
Indexed
2026-08-20 20:33

inicio - Wiki
Copyright © 2011-2026 iteam. Current version is 2.155.2. UTC+08:00, 2026-10-04 11:15
浙ICP备14020137号-1