using-base-ui-with-material-ui
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseAnnounce on start: You must announce "Using Base UI with Material UI skill" when this skill is invoked.
Always have enough context from the Base UI documentation to build the component requested by the user.
启动时提示:当调用本技能时,你必须提示“Using Base UI with Material UI skill”。
请始终从Base UI文档获取足够的上下文,以构建用户请求的组件。
Base UI as the foundation
以Base UI为基础
Render Base UI components as a foundation for the UI and then pass prop using proper Material UI components.
renderFor example, a Navigation Menu, should use from Material UI as the render element for .:
LinkNavigationMenu.Linktsx
import { NavigationMenu } from "@base-ui-components/react/navigation-menu";
import Box from "@mui/material/Box";
import Link from "@mui/material/Link";
import Typography from "@mui/material/Typography";
function MenuLink({
icon,
title,
description,
...props
}: NavigationMenu.Link.Props & {
icon?: React.ReactNode;
title: string;
description: string;
}) {
return (
<NavigationMenu.Link
href="#"
{...props}
render={
<Link
underline="none"
sx={{
display: "flex",
gap: 1,
p: 1.5,
borderRadius: 0.5,
cursor: "pointer",
transition: "background-color 0.2s",
"@media (hover: hover)": {
"&:hover": {
bgcolor: "action.hover",
},
},
}}
/>
}
>
<Box sx={{ color: "primary.main", display: "flex", mt: 0.25 }}>
{icon}
</Box>
<Box>
<Typography variant="subtitle2" sx={{ fontWeight: 600, mb: 0.25 }}>
{title}
</Typography>
<Typography
variant="body2"
sx={{ color: "text.secondary", lineHeight: 1.4 }}
>
{description}
</Typography>
</Box>
</NavigationMenu.Link>
);
}For full example, see nav-menu-01.tsx
Another example, using from Material UI as the render element for Base UI component:
ButtonTriggertsx
import { Menu } from "@base-ui-components/react/menu";
import Button from "@mui/material/Button";
<Menu.Trigger render={<Button />}>File</Menu.Trigger>;将Base UI组件作为UI的基础进行渲染,然后使用合适的Material UI组件传递属性。
render例如,导航菜单应使用Material UI的作为的渲染元素:
LinkNavigationMenu.Linktsx
import { NavigationMenu } from "@base-ui-components/react/navigation-menu";
import Box from "@mui/material/Box";
import Link from "@mui/material/Link";
import Typography from "@mui/material/Typography";
function MenuLink({
icon,
title,
description,
...props
}: NavigationMenu.Link.Props & {
icon?: React.ReactNode;
title: string;
description: string;
}) {
return (
<NavigationMenu.Link
href="#"
{...props}
render={
<Link
underline="none"
sx={{
display: "flex",
gap: 1,
p: 1.5,
borderRadius: 0.5,
cursor: "pointer",
transition: "background-color 0.2s",
"@media (hover: hover)": {
"&:hover": {
bgcolor: "action.hover",
},
},
}}
/>
}
>
<Box sx={{ color: "primary.main", display: "flex", mt: 0.25 }}>
{icon}
</Box>
<Box>
<Typography variant="subtitle2" sx={{ fontWeight: 600, mb: 0.25 }}>
{title}
</Typography>
<Typography
variant="body2"
sx={{ color: "text.secondary", lineHeight: 1.4 }}
>
{description}
</Typography>
</Box>
</NavigationMenu.Link>
);
}完整示例请查看nav-menu-01.tsx
另一个示例,使用Material UI的作为Base UI 组件的渲染元素:
ButtonTriggertsx
import { Menu } from "@base-ui-components/react/menu";
import Button from "@mui/material/Button";
<Menu.Trigger render={<Button />}>File</Menu.Trigger>;Styling
样式处理
To style Base UI components, use as a render element and pass prop to it.
Always keep in mind that the sx values should be minimum since Material UI components already have default styling.
<Box />sxtsx
import { NavigationMenu } from "@base-ui-components/react/navigation-menu";
import Box from "@mui/material/Box";
<NavigationMenu.List
render={
<Box
component="ul"
sx={{
display: "flex",
justifyContent: "center",
gap: 2,
listStyle: "none",
"& .MuiButton-root[data-popup-open]": {
bgcolor: "action.selected",
},
}}
/>
}
></NavigationMenu.List>;要为Base UI组件设置样式,请使用作为渲染元素,并为其传递属性。请始终记住,由于Material UI组件已有默认样式,sx值应尽可能精简。
<Box />sxtsx
import { NavigationMenu } from "@base-ui-components/react/navigation-menu";
import Box from "@mui/material/Box";
<NavigationMenu.List
render={
<Box
component="ul"
sx={{
display: "flex",
justifyContent: "center",
gap: 2,
listStyle: "none",
"& .MuiButton-root[data-popup-open]": {
bgcolor: "action.selected",
},
}}
/>
}
></NavigationMenu.List>;Primitive/Non-interactive Components
基础/非交互式组件
For non-interactive Base UI components like Meter, Progress, Slider (read-only), etc. that don't have direct semantic Material UI equivalents, always use the prop pattern with .
renderBoxCRITICAL: Never use - this is incorrect and causes issues. Always use Base UI components as the foundation with the prop.
component={BaseUIComponent}render对于Meter、Progress、只读Slider等没有直接对应语义Material UI组件的非交互式Base UI组件,请始终结合使用属性模式。
Boxrender重要提示:绝不要使用——这是错误的用法,会引发问题。请始终以Base UI组件为基础,使用属性。
component={BaseUIComponent}render✅ Correct Pattern
✅ 正确模式
tsx
import { Meter } from "@base-ui-components/react/meter";
import Box from "@mui/material/Box";
<Meter.Track
render={
<Box
sx={{
height: 8,
width: "100%",
bgcolor: "action.disabledBackground",
borderRadius: 1,
overflow: "hidden",
position: "relative",
}}
/>
}
>
<Meter.Indicator
render={
<Box
sx={{
height: "100%",
bgcolor: "text.primary",
transition: "width 0.3s ease",
}}
/>
}
/>
</Meter.Track>;tsx
import { Meter } from "@base-ui-components/react/meter";
import Box from "@mui/material/Box";
<Meter.Track
render={
<Box
sx={{
height: 8,
width: "100%",
bgcolor: "action.disabledBackground",
borderRadius: 1,
overflow: "hidden",
position: "relative",
}}
/>
}
>
<Meter.Indicator
render={
<Box
sx={{
height: "100%",
bgcolor: "text.primary",
transition: "width 0.3s ease",
}}
/>
}
/>
</Meter.Track>;❌ Incorrect Pattern
❌ 错误模式
tsx
// ❌ NEVER do this - Base UI should be the foundation, not MUI Box
<Box component={Meter.Track} sx={{ ... }}>
<Box component={Meter.Indicator} sx={{ ... }} />
</Box>
// ❌ NEVER do this - Using asChild prop (not a React pattern)
<Meter.Track asChild>
<Box sx={{ ... }}>
<Meter.Indicator asChild>
<Box sx={{ ... }} />
</Meter.Indicator>
</Box>
</Meter.Track>tsx
// ❌ 绝不要这样做——Base UI应作为基础,而非MUI Box
<Box component={Meter.Track} sx={{ ... }}>
<Box component={Meter.Indicator} sx={{ ... }} />
</Box>
// ❌ 绝不要这样做——使用asChild属性(这不是React的标准模式)
<Meter.Track asChild>
<Box sx={{ ... }}>
<Meter.Indicator asChild>
<Box sx={{ ... }} />
</Meter.Indicator>
</Box>
</Meter.Track>Key Points
核心要点
- Base UI First: Always render Base UI components as the outer wrapper
- render Prop: Use to apply Material UI styling
render={<Box sx={{ ... }} />} - Theme Tokens: Use MUI theme tokens in sx prop (e.g., ,
bgcolor: "action.hover")color: "text.primary" - Minimal Styling: Keep sx props minimal - only add what's necessary for the design
- 优先使用Base UI:始终将Base UI组件作为外层容器渲染
- 使用render属性:通过应用Material UI样式
render={<Box sx={{ ... }} />} - 主题令牌:在sx属性中使用MUI主题令牌(例如、
bgcolor: "action.hover")color: "text.primary" - 精简样式:保持sx属性精简——仅添加设计所需的必要样式
Reduce duplication
减少代码重复
If the same styles are used multiple times for the same Base UI components, create wrapper components to reduce duplication.
tsx
import { NavigationMenu } from "@base-ui-components/react/navigation-menu";
function Content(props: BoxProps) {
return (
<Box
sx={{
padding: 1,
width: "calc(100vw - 40px)",
height: "100%",
"@media (min-width: 500px)": {
width: "max-content",
minWidth: "400px",
},
}}
{...props}
/>
);
}
<NavigationMenu.List>
<NavigationMenu.Item>
<NavigationMenu.Content render={<Content />}></NavigationMenu.Content>
</NavigationMenu.Item>
<NavigationMenu.Item>
<NavigationMenu.Content render={<Content />}></NavigationMenu.Content>
</NavigationMenu.Item>
<NavigationMenu.Item>
<NavigationMenu.Content render={<Content />}></NavigationMenu.Content>
</NavigationMenu.Item>
</NavigationMenu.List>;如果同一Base UI组件多次使用相同样式,请创建包装组件以减少重复。
tsx
import { NavigationMenu } from "@base-ui-components/react/navigation-menu";
function Content(props: BoxProps) {
return (
<Box
sx={{
padding: 1,
width: "calc(100vw - 40px)",
height: "100%",
"@media (min-width: 500px)": {
width: "max-content",
minWidth: "400px",
},
}}
{...props}
/>
);
}
<NavigationMenu.List>
<NavigationMenu.Item>
<NavigationMenu.Content render={<Content />}></NavigationMenu.Content>
</NavigationMenu.Item>
<NavigationMenu.Item>
<NavigationMenu.Content render={<Content />}></NavigationMenu.Content>
</NavigationMenu.Item>
<NavigationMenu.Item>
<NavigationMenu.Content render={<Content />}></NavigationMenu.Content>
</NavigationMenu.Item>
</NavigationMenu.List>;TypeScript Props Interface
TypeScript属性接口
CRITICAL: When creating wrapper components around Base UI primitives, NEVER duplicate props that are already provided by the Base UI component.
重要提示:在围绕Base UI基础组件创建包装组件时,绝不要重复Base UI组件已提供的属性。
❌ Incorrect - Duplicating Base UI Props
❌ 错误示例——重复Base UI属性
tsx
import { PreviewCard } from "@base-ui-components/react/preview-card";
// ❌ BAD: Manually duplicating delay, closeDelay, defaultOpen, etc.
export interface CardPreview01Props {
trigger: React.ReactNode;
href: string;
delay?: number; // Already in PreviewCard.Root.Props
closeDelay?: number; // Already in PreviewCard.Root.Props
defaultOpen?: boolean; // Already in PreviewCard.Root.Props
open?: boolean; // Already in PreviewCard.Root.Props
onOpenChange?: (open: boolean) => void; // Already in PreviewCard.Root.Props
}tsx
import { PreviewCard } from "@base-ui-components/react/preview-card";
// ❌ 错误:手动重复delay、closeDelay、defaultOpen等属性
export interface CardPreview01Props {
trigger: React.ReactNode;
href: string;
delay?: number; // 已包含在PreviewCard.Root.Props中
closeDelay?: number; // 已包含在PreviewCard.Root.Props中
defaultOpen?: boolean; // 已包含在PreviewCard.Root.Props中
open?: boolean; // 已包含在PreviewCard.Root.Props中
onOpenChange?: (open: boolean) => void; // 已包含在PreviewCard.Root.Props中
}✅ Correct - Extending Base UI Props
✅ 正确示例——扩展Base UI属性
tsx
import { PreviewCard } from "@base-ui-components/react/preview-card";
// ✅ GOOD: Extend the Base UI component props
export interface CardPreview01Props extends PreviewCard.Root.Props {
trigger: React.ReactNode;
href: string;
imageSrc: string;
imageAlt: string;
heading: string;
description: string;
}
export function CardPreview01({
trigger,
href,
imageSrc,
imageAlt,
heading,
description,
...props // This spreads all Base UI props (delay, closeDelay, defaultOpen, etc.)
}: CardPreview01Props) {
return (
<PreviewCard.Root {...props}>{/* component content */}</PreviewCard.Root>
);
}tsx
import { PreviewCard } from "@base-ui-components/react/preview-card";
// ✅ 正确:扩展Base UI组件的属性
export interface CardPreview01Props extends PreviewCard.Root.Props {
trigger: React.ReactNode;
href: string;
imageSrc: string;
imageAlt: string;
heading: string;
description: string;
}
export function CardPreview01({
trigger,
href,
imageSrc,
imageAlt,
heading,
description,
...props // 展开所有Base UI属性(delay、closeDelay、defaultOpen等)
}: CardPreview01Props) {
return (
<PreviewCard.Root {...props}>{/* 组件内容 */}</PreviewCard.Root>
);
}Key Benefits
核心优势
- Type Safety: Automatically get all Base UI prop types without manual maintenance
- Future-Proof: New Base UI props automatically available in your component
- No Duplication: Single source of truth for prop definitions
- Better DX: TypeScript autocomplete shows all available props
- 类型安全:无需手动维护,自动获取所有Base UI属性类型
- 面向未来:Base UI新增的属性会自动在你的组件中可用
- 无重复:属性定义单一可信来源
- 更好的开发体验:TypeScript自动补全会显示所有可用属性
When to Define Custom Props
何时定义自定义属性
Only define props that are:
- Specific to your wrapper component (like ,
imageSrc)heading - Not part of the underlying Base UI component
- Required for your custom implementation logic
仅当属性满足以下条件时才定义:
- 特定于你的包装组件(例如、
imageSrc)heading - 不属于底层Base UI组件
- 你的自定义实现逻辑需要该属性