using-base-ui-with-material-ui

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese
Announce 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
render
prop using proper Material UI components.
For example, a Navigation Menu, should use
Link
from Material UI as the render element for
NavigationMenu.Link
.:
tsx
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
Button
from Material UI as the render element for Base UI
Trigger
component:
tsx
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的
Link
作为
NavigationMenu.Link
的渲染元素:
tsx
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的
Button
作为Base UI
Trigger
组件的渲染元素:
tsx
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
<Box />
as a render element and pass
sx
prop to it. Always keep in mind that the sx values should be minimum since Material UI components already have default styling.
tsx
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组件设置样式,请使用
<Box />
作为渲染元素,并为其传递
sx
属性。请始终记住,由于Material UI组件已有默认样式,sx值应尽可能精简。
tsx
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
render
prop pattern with
Box
.
CRITICAL: Never use
component={BaseUIComponent}
- this is incorrect and causes issues. Always use Base UI components as the foundation with the
render
prop.
对于Meter、Progress、只读Slider等没有直接对应语义Material UI组件的非交互式Base UI组件,请始终结合
Box
使用
render
属性模式
重要提示:绝不要使用
component={BaseUIComponent}
——这是错误的用法,会引发问题。请始终以Base UI组件为基础,使用
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

核心要点

  1. Base UI First: Always render Base UI components as the outer wrapper
  2. render Prop: Use
    render={<Box sx={{ ... }} />}
    to apply Material UI styling
  3. Theme Tokens: Use MUI theme tokens in sx prop (e.g.,
    bgcolor: "action.hover"
    ,
    color: "text.primary"
    )
  4. Minimal Styling: Keep sx props minimal - only add what's necessary for the design
  1. 优先使用Base UI:始终将Base UI组件作为外层容器渲染
  2. 使用render属性:通过
    render={<Box sx={{ ... }} />}
    应用Material UI样式
  3. 主题令牌:在sx属性中使用MUI主题令牌(例如
    bgcolor: "action.hover"
    color: "text.primary"
  4. 精简样式:保持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

核心优势

  1. Type Safety: Automatically get all Base UI prop types without manual maintenance
  2. Future-Proof: New Base UI props automatically available in your component
  3. No Duplication: Single source of truth for prop definitions
  4. Better DX: TypeScript autocomplete shows all available props
  1. 类型安全:无需手动维护,自动获取所有Base UI属性类型
  2. 面向未来:Base UI新增的属性会自动在你的组件中可用
  3. 无重复:属性定义单一可信来源
  4. 更好的开发体验: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组件
  • 你的自定义实现逻辑需要该属性