响应式设计与容器查询
组件库不是页面,不能用媒体查询写死断点。容器查询让组件根据自身容器大小自适应。
组件库的响应式挑战
传统响应式用媒体查询(@media),比如 @media (max-width: 768px)。但组件库有一个根本问题:组件不知道自己被放在多大的容器里。
同一个 ChatWindow,可能占满全屏(宽度 1200px),也可能嵌在侧边栏里(宽度 360px)。如果用媒体查询写死断点,在侧边栏里即使屏幕宽度 1920px,组件也只有 360px 宽,但媒体查询认为这是桌面尺寸。
解决方案:容器查询(Container Queries)。
容器查询基础
/* 定义容器 */
.ai-chat {
container-type: inline-size;
container-name: chat;
}
/* 根据容器宽度响应 */
@container chat (max-width: 480px) {
.ai-chat__header {
flex-direction: column;
}
.ai-chat__input {
flex-direction: column;
}
}
css
容器查询检查的是组件自身容器的宽度,而不是视口宽度。这在组件库场景下完全正确。
断点设计
基于内容而非设备定义断点。ai-chat-ui 在 tokens.css 原始层定义了断点令牌(文档作用,容器查询条件里不能直接用 var(),见下文):
--ai-chat-breakpoint-sm: 480px;
--ai-chat-breakpoint-md: 640px;
--ai-chat-breakpoint-lg: 1024px;
--ai-chat-breakpoint-xl: 1280px;
css
三个关键断点的响应策略:
480px 以下(紧凑模式):
- 输入区域垂直排列(发送按钮在输入框下方)
- 头像缩小或隐藏
- 消息间距收紧
- 代码块横向滚动
480px - 640px(标准模式):
- 输入区域水平排列
- 标准头像尺寸
- 正常消息间距
640px 以上(宽松模式):
- 可以显示侧边栏或用户列表
- 更大的气泡圆角和间距
- 代码块有行号显示
具体实现(仓库真实用法)
组件库里没有集中一个 responsive.css,而是谁需要响应谁就是容器。真实代码三处(节选自仓库源码):
输入区收窄(packages/vue/src/prompt-input/PromptInput.vue)——容器是输入组件自身,窄屏降 padding、隐藏免责声明:
.ai-chat-prompt-input {
max-width: var(--ai-chat-content-max-width);
width: 100%;
margin: 0 auto;
padding: 0 24px 20px;
container-type: inline-size;
}
/* 窄屏(移动端):降低 padding、隐藏 disclaimer。
容器查询条件不支持 var(),640px 为字面量(= --ai-chat-breakpoint-md 默认)。 */
@container (max-width: 640px) {
.ai-chat-prompt-input {
padding: 0 12px 12px;
}
.ai-chat-prompt-input__disclaimer {
display: none;
}
}
css
消息区放宽(packages/vue/src/conversation/ConversationContent.vue)——宽屏时消息内容区从 768px 放宽到 1024px:
.ai-chat-conversation-content {
flex: 1;
overflow-y: auto;
container-type: inline-size;
}
/* 宽屏:消息区放宽。
注意:容器查询条件不支持 var(),此处 1280px 为字面量(= --ai-chat-breakpoint-xl 默认)。 */
@container (min-width: 1280px) {
.ai-chat-conversation-messages {
max-width: var(--ai-chat-content-max-width-wide);
}
}
css
对比消息堆叠(packages/vue/src/comparison/ComparisonMessage.vue)——双栏对比在窄容器下改单栏:
@container (max-width: 640px) {
.ai-chat-comparison.is-stack-mobile {
grid-template-columns: 1fr;
}
}
css
一个真实踩坑:@container 的查询条件不支持 var()——断点值只能写字面量。仓库的做法是把断点令牌留在 tokens.css 当文档,查询处写死数字并加注释标明"该字面量对应哪个断点令牌的默认值",改断点时全库搜字面量。
Composable 配合
纯样式变化直接用容器查询 CSS 就够;只有需要 JavaScript 逻辑响应容器宽度时(比如按宽度决定渲染哪个组件、是否虚拟滚动),才用 ResizeObserver 补一条 JS 通道:
import { ref, onMounted, onUnmounted } from 'vue'
type Breakpoint = 'compact' | 'standard' | 'wide'
export function useResponsive(containerRef: Ref<HTMLElement | undefined>) {
const breakpoint = ref<Breakpoint>('standard')
onMounted(() => {
if (!containerRef.value) return
const observer = new ResizeObserver((entries) => {
const width = entries[0].contentRect.width
if (width <= 480) breakpoint.value = 'compact'
else if (width <= 640) breakpoint.value = 'standard'
else breakpoint.value = 'wide'
})
observer.observe(containerRef.value)
onUnmounted(() => observer.disconnect())
})
return { breakpoint }
}
ts
(ai-chat-ui 目前没有抽出这个 composable——三处容器查询覆盖了全部需求,YAGNI;上面是将来需要时的标准写法。)
浏览器兼容性
容器查询的浏览器支持情况(2026 年核实):
- 尺寸容器查询(
container-type: inline-size+@container):Chrome/Edge 105+、Safari 16+、Firefox 110+,2023 年起 Baseline,四大引擎全量落地; - 全球覆盖率约 93%–95%(2026 年初,snipshift.dev 等口径;此前笔记写的"97%+"偏乐观,已修正);
- 样式容器查询(style queries,按自定义属性值查询)截至 2026 年中仍在跟进中,不建议生产使用。
如果需要兼容更老的浏览器,fallback 用媒体查询:
/* Fallback: 不支持容器查询时用视口宽度 */
@supports not (container-type: inline-size) {
@media (max-width: 640px) {
.ai-chat-prompt-input__wrapper {
flex-direction: column;
}
}
}
css
用AI生成响应式样式
基于 ai-chat-ui 组件的现有样式,生成容器查询响应式CSS。
要求:
1. 使用 @container 而非 @media
2. 三个断点:480px / 640px
3. 紧凑模式优化:输入框垂直排列、头像缩小、代码块字号减小
4. 为不支持容器查询的浏览器提供 @media fallback
text
小结
- 组件库用容器查询而非媒体查询——响应的是容器宽度而非视口宽度;谁需要响应谁就是容器(仓库三处实战:输入区 640px 收窄、消息区 1280px 放宽、对比消息窄容器堆叠)
- 断点令牌:480 / 640 / 1024 / 1280;容器查询条件不支持
var(),查询处写字面量 + 注释锚定令牌 - 纯样式变化用 CSS 容器查询,需要 JS 逻辑时才考虑 ResizeObserver(当前仓库未抽 composable,YAGNI)
- 尺寸容器查询 2023 年起 Baseline,2026 年覆盖率约 93%–95%,老浏览器用 @media fallback;样式查询(style queries)尚未普及,别上生产
- AI 可以扫描组件样式自动生成响应式规则
下一节实现组件样式隔离——确保组件库样式不污染宿主应用。
↑