Skip to content

feat: container query layout for ModalForm/DrawerForm mobile single-column#670

Merged
hotlong merged 3 commits intomainfrom
copilot/optimize-modalform-layout
Feb 21, 2026
Merged

feat: container query layout for ModalForm/DrawerForm mobile single-column#670
hotlong merged 3 commits intomainfrom
copilot/optimize-modalform-layout

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Feb 21, 2026

ModalForm uses viewport-based md:grid-cols-2 (768px breakpoint) for multi-column layout. On high-res phones where viewport ≥ 768px, the modal renders two columns despite being ~350px wide — unusable on mobile.

Switches to CSS Container Queries so the form grid responds to the modal's actual width, not the viewport.

Changes

  • ModalForm / DrawerForm: Add @container on content wrapper; pass fieldContainerClass with container-query grid classes (@md:grid-cols-2 at container ≥ 448px) to SchemaRenderer
  • FormSection: New gridClassName prop to override viewport-based grid classes from parent context
  • Tests: 3 new tests — @container presence, container-query grid for multi-col, no override for single-col
  • ROADMAP.md: Updated Mobile UX section

Breakpoint behavior

Container width Columns
< 448px (phones) 1
≥ 448px (@md) 2
≥ 672px (@2xl) 3
≥ 896px (@4xl) 4
// Before: viewport-based — breaks on high-res phones
<div className="flex-1 overflow-y-auto ...">

// After: container-query-based — responds to modal width
<div className="@container flex-1 overflow-y-auto ...">

// Grid classes passed via fieldContainerClass
const CONTAINER_GRID_COLS = {
  1: undefined,
  2: 'grid gap-4 grid-cols-1 @md:grid-cols-2',
  3: 'grid gap-4 grid-cols-1 @md:grid-cols-2 @2xl:grid-cols-3',
  4: 'grid gap-4 grid-cols-1 @md:grid-cols-2 @2xl:grid-cols-3 @4xl:grid-cols-4',
};

Tailwind v4 supports container queries natively — no plugin needed. The existing fieldContainerClass prop on the form renderer is used to override the default viewport-based grid, keeping the change fully backward-compatible.

Original prompt

This section details on the original issue you should resolve

<issue_title>[表单/弹窗深度优化] ModalForm 等弹窗表单支持容器查询布局及极致移动端单列体验(container query + 长远可扩展)</issue_title>
<issue_description>## 背景和问题说明
当前 ModalForm 在移动端表单仍可能出现两列布局,带来可用性和可访问性问题,详见下述截图:

移动端表单两列问题截图

现有实现采用 Tailwind md: 断点,仅依据 viewport 宽度,而非 modal 实际容器宽度,导致部分高分辨率移动设备和小平板在 modal 全屏时仍为两列,交互密度偏高且不易用。此外,随着表单字段数增多,auto-layout 在移动端场景对"多列"的推断不总是最优。

长远、深度优化目标

  • 彻底解决 ModalForm 在移动端任何场景下都只能单列,且适应动态表单和复杂场景。
  • 支持未来下拉抽屉/弹窗等不同形态都可继承这类优化能力。
  • 解耦表单 grid 布局断点与 viewport,采用容器查询(container query)实现按 modal 宽度自适应。
  • 优化字段间距、主操作区可达性、Label/Description 样式、骨架屏等综合体验。
  • 提供可扩展的自定义断点和布局逻辑,兼具全局表单和弹窗局部表单的最佳实践。

拟实施技术方案(建议路线)

  1. 表单 grid 布局切换到 CSS Container Query(container-based grid columns)
    • 升级 Tailwind 到支持 container queries 的版本,开启相应插件。
    • 将原 md:grid-cols-2 等响应式断点迁移为 @md:grid-cols-2 等 container 断点。
    • ModalForm、DrawerForm、全页表单均切换为 @container
    • 移动端全屏 modal/container 内,无论 viewport 宽度,只要弹窗实际宽度达不到指定断点,都自动单列。
  2. ModalForm 针对性保障移动端单列
    • 临时过渡期,ModalForm renderContent 传递专用 fieldContainerClass,移动端断点提升到 lg:,确保桌面前都单列。
    • 或通过 JS 检测,强制 columns=1(如需快速兜底)。
  3. 主操作按钮顺序与布局优化
    • 移动端"主操作按钮置于上方、居中、放大 touch 区域,Cancel 放下方。
    • gap/gap-y 增大,label/description 字号提升。
  4. Skeleton 骨架屏智能生成/自适应列数
    • 骨架 Loader 可根据表单实际字段数及��数动态渲染。
  5. 文档与测试用例完善
    • 新增复杂表单/长表单的容器宽度自适应用例。
    • 完善 Roadmap 和核心文档,声明推荐"container query"开发模式。

附加建议

  • 将表单、modal、drawer 等组件的容器查询能力可配置暴露,便于后续不同业务场景定制优化。
  • 如果有旧项目或第三方库兼容问题,保留旧断点逻辑的 fallback。

  • 完成后务必运行所有相关 test 并更新 ROADMAP。
  • 参考本 issue 讨论产出 PR。</issue_description>

Comments on the Issue (you are @copilot in this section)


🔒 GitHub Advanced Security automatically protects Copilot coding agent pull requests. You can protect all pull requests by enabling Advanced Security for your repositories. Learn more about Advanced Security.

@vercel
Copy link
Copy Markdown

vercel Bot commented Feb 21, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
objectui Ready Ready Preview, Comment Feb 21, 2026 2:28am
objectui-demo Ready Ready Preview, Comment Feb 21, 2026 2:28am
objectui-storybook Ready Ready Preview, Comment Feb 21, 2026 2:28am

Request Review

Copilot AI and others added 2 commits February 21, 2026 02:02
…le single-column

- Add @container to ModalForm/DrawerForm content wrappers
- Use @md:grid-cols-2 (container query) instead of md:grid-cols-2 (viewport)
- Add gridClassName prop to FormSection for container-query override
- Add 3 new tests for container query behavior
- Update ROADMAP.md with container query support status

Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
…implify assertion

Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
Copilot AI changed the title [WIP] Optimize ModalForm for single column layout on mobile feat: container query layout for ModalForm/DrawerForm mobile single-column Feb 21, 2026
Copilot AI requested a review from hotlong February 21, 2026 02:07
@hotlong hotlong marked this pull request as ready for review February 21, 2026 02:10
Copilot AI review requested due to automatic review settings February 21, 2026 02:10
@hotlong hotlong merged commit 7f6d79f into main Feb 21, 2026
4 of 6 checks passed
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Updates the form modal/drawer layouts to use CSS container queries so multi-column form grids respond to the actual Modal/Drawer width (fixing high-res mobile viewports incorrectly triggering 2-column layouts).

Changes:

  • Add @container contexts in ModalForm and DrawerForm, and pass container-query grid classes (@md: / @2xl: / @4xl:) into the form renderer via fieldContainerClass.
  • Extend FormSection with a gridClassName override to support container-query-based section grids.
  • Add container-query layout tests for ModalForm and update the Mobile UX section in ROADMAP.md.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
packages/plugin-form/src/tests/MobileUX.test.tsx Adds tests asserting @container context and container-query grid usage in ModalForm.
packages/plugin-form/src/ModalForm.tsx Adds container-query grid class mapping and applies @container to the scroll area; passes fieldContainerClass for flat-field layouts and section overrides.
packages/plugin-form/src/FormSection.tsx Adds gridClassName prop to allow overriding default viewport breakpoint grid classes.
packages/plugin-form/src/DrawerForm.tsx Mirrors ModalForm container-query approach for drawers and adds @container wrapper.
ROADMAP.md Updates roadmap notes to reflect the container-query-based layout behavior.

Comment on lines +144 to 145
<div className={cn('grid gap-4', gridClassName || gridCols[columns])}>
{children}
Copy link

Copilot AI Feb 21, 2026

Choose a reason for hiding this comment

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

gridClassName is treated as an override, but it’s still combined with the hard-coded 'grid gap-4'. This both prevents callers from changing the gap and can produce duplicated classes (e.g. passing 'grid gap-4 ...' from ModalForm/DrawerForm results in 'grid gap-4 grid gap-4 ...'). Consider making gridClassName a full override (use it instead of the default cn('grid gap-4', gridCols[columns])), or rename it to indicate it only overrides the grid-cols portion and strip grid/gap from the values passed in.

Copilot uses AI. Check for mistakes.
expect(gridEl).not.toBeNull();
expect(gridEl!.className).toContain('@md:grid-cols-2');
// Should NOT use viewport-based md:grid-cols-2 (without @ prefix)
expect(gridEl!.className).not.toContain(' md:grid-cols-2');
Copy link

Copilot AI Feb 21, 2026

Choose a reason for hiding this comment

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

This assertion can miss a regression because it only checks for ' md:grid-cols-2' (with a leading space). If the class list contains 'md:grid-cols-2' at the beginning (or without a preceding space), the test will still pass. Suggest checking for 'md:grid-cols-2' more robustly (e.g. tokenizing classes and ensuring the md:grid-cols-2 token is absent, while still allowing @md:grid-cols-2).

Suggested change
expect(gridEl!.className).not.toContain(' md:grid-cols-2');
const classTokens = gridEl!.className.split(/\s+/);
expect(classTokens).not.toContain('md:grid-cols-2');

Copilot uses AI. Check for mistakes.
Comment on lines +108 to +119
/**
* Container-query-based grid classes for form field layout.
* Uses @container / @md: / @2xl: / @4xl: variants so that the grid
* responds to the modal's actual width instead of the viewport,
* ensuring single-column on narrow mobile modals regardless of viewport size.
*/
const CONTAINER_GRID_COLS: Record<number, string | undefined> = {
1: undefined, // let the form renderer use its default (space-y-4)
2: 'grid gap-4 grid-cols-1 @md:grid-cols-2',
3: 'grid gap-4 grid-cols-1 @md:grid-cols-2 @2xl:grid-cols-3',
4: 'grid gap-4 grid-cols-1 @md:grid-cols-2 @2xl:grid-cols-3 @4xl:grid-cols-4',
};
Copy link

Copilot AI Feb 21, 2026

Choose a reason for hiding this comment

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

CONTAINER_GRID_COLS is duplicated in both ModalForm and DrawerForm. To avoid the two mappings drifting over time (breakpoints/classes changing in one place but not the other), consider extracting this constant to a shared module (e.g. packages/plugin-form/src/containerGridCols.ts) and importing it from both components.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[表单/弹窗深度优化] ModalForm 等弹窗表单支持容器查询布局及极致移动端单列体验(container query + 长远可扩展)

3 participants