organizing-classnames

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

ClassNames Usage and Conventions

ClassNames 使用规范与约定

1. Import and Component Props Pattern

1. 导入与组件属性模式

  • Import
    classNames
    from
    'classnames'
    alongside other third-party imports
  • The
    className
    prop must be optional:
    className?: string
  • Always merge the consumer's
    className
    last so it can override defaults:
tsx
className={classNames('default-classes', className)}
  • 'classnames'
    导入
    classNames
    ,与其他第三方包导入放在一起
  • className
    属性必须是可选的:
    className?: string
  • 始终将使用者传入的
    className
    最后 合并,以便它可以覆盖默认样式:
tsx
className={classNames('default-classes', className)}

2. Breakpoint Order (Mobile-First)

2. 断点顺序(移动端优先)

Order classes from smallest to largest screen size — always:
base (no prefix) → xs: → s: → sm: → md: → m: → lg: → xl: → 2xl:
Check the project's
tailwind.config
for the actual breakpoint names — they may differ.
类的顺序必须从最小屏幕尺寸到最大屏幕尺寸排列:
base(无前缀)→ xs: → s: → sm: → md: → m: → lg: → xl: → 2xl:
请查看项目的
tailwind.config
文件确认实际的断点名称——它们可能有所不同。

3. Class Organization Rules

3. 类组织规则

  • Group by breakpoint — each breakpoint gets its own string argument
  • Order utilities logically within each group: layout → spacing → typography → colors → effects
  • No trailing comma after the last
    classNames
    argument
  • 按断点分组——每个断点单独作为一个字符串参数
  • 在每组内按逻辑排序工具类:布局 → 间距 → 排版 → 颜色 → 特效
  • classNames
    的最后一个参数后不要加尾随逗号

4. Patterns

4. 示例模式

Basic Breakpoint Ordering

基础断点排序

tsx
className={classNames(
  'base-style-1 base-style-2',
  'xs:xs-style-1 xs:xs-style-2',
  'sm:sm-style-1 sm:sm-style-2',
  'md:md-style-1 md:md-style-2',
  'lg:lg-style-1 lg:lg-style-2',
  'xl:xl-style-1 xl:xl-style-2',
  '2xl:2xl-style-1 2xl:2xl-style-2'
)}
tsx
className={classNames(
  'base-style-1 base-style-2',
  'xs:xs-style-1 xs:xs-style-2',
  'sm:sm-style-1 sm:sm-style-2',
  'md:md-style-1 md:md-style-2',
  'lg:lg-style-1 lg:lg-style-2',
  'xl:xl-style-1 xl:xl-style-2',
  '2xl:2xl-style-1 2xl:2xl-style-2'
)}

Conditional Classes + Passed
className
Prop

条件类 + 传入的
className
属性

tsx
type Props = {
  size?: ButtonSize;
  variant?: ButtonVariant;
  className?: string;
};

export const Button: React.FC<Props> = ({
  children,
  size = ButtonSize.MEDIUM,
  variant = ButtonVariant.PRIMARY,
  className,
  ...props
}) => (
  <button
    className={classNames(
      "block rounded-lg text-center transition",
      "disabled:opacity-50",
      { "px-3 py-2 text-sm-medium": size === ButtonSize.SMALL },
      { "px-4 py-3 text-md-medium": size === ButtonSize.MEDIUM },
      {
        "bg-orange-500 text-white hover:bg-orange-700":
          variant === ButtonVariant.PRIMARY,
      },
      {
        "outline outline-gray-500 hover:text-orange-500":
          variant === ButtonVariant.SECONDARY,
      },
      { "pointer-events-none": Boolean(props?.disabled) },
      className,
    )}
    type="button"
    {...props}
  >
    {children}
  </button>
);
tsx
type Props = {
  size?: ButtonSize;
  variant?: ButtonVariant;
  className?: string;
};

export const Button: React.FC<Props> = ({
  children,
  size = ButtonSize.MEDIUM,
  variant = ButtonVariant.PRIMARY,
  className,
  ...props
}) => (
  <button
    className={classNames(
      "block rounded-lg text-center transition",
      "disabled:opacity-50",
      { "px-3 py-2 text-sm-medium": size === ButtonSize.SMALL },
      { "px-4 py-3 text-md-medium": size === ButtonSize.MEDIUM },
      {
        "bg-orange-500 text-white hover:bg-orange-700":
          variant === ButtonVariant.PRIMARY,
      },
      {
        "outline outline-gray-500 hover:text-orange-500":
          variant === ButtonVariant.SECONDARY,
      },
      { "pointer-events-none": Boolean(props?.disabled) },
      className,
    )}
    type="button"
    {...props}
  >
    {children}
  </button>
);

Multiple
className
Props for Sub-elements

子元素的多个
className
属性

Expose separate className props for each styleable sub-element:
tsx
type Props = {
  title: ReactNode;
  children: ReactNode;
  className?: string;
  headerClassName?: string;
  contentClassName?: string;
};

export const Card: React.FC<Props> = ({
  title,
  children,
  className,
  headerClassName,
  contentClassName,
}) => (
  <div className={classNames("rounded-lg border", className)}>
    <div className={classNames("border-b p-4", headerClassName)}>{title}</div>
    <div className={classNames("p-4", contentClassName)}>{children}</div>
  </div>
);
为每个可设置样式的子元素单独暴露className属性:
tsx
type Props = {
  title: ReactNode;
  children: ReactNode;
  className?: string;
  headerClassName?: string;
  contentClassName?: string;
};

export const Card: React.FC<Props> = ({
  title,
  children,
  className,
  headerClassName,
  contentClassName,
}) => (
  <div className={classNames("rounded-lg border", className)}>
    <div className={classNames("border-b p-4", headerClassName)}>{title}</div>
    <div className={classNames("p-4", contentClassName)}>{children}</div>
  </div>
);

Shared Styles (Extract Reused Class Sets)

共享样式(提取复用的类集合)

Extract class sets into a variable only when they are reused by multiple elements in the same component or when the repeated class list clearly hurts readability. Do not extract one-off class sets that are used only once.
When multiple elements share the same base classes, extract to a variable:
tsx
const Buttons = () => {
  const buttonClassName = classNames(
    "flex w-full shrink-0 items-center justify-center transition-colors",
    "lg:cursor-pointer",
  );

  return (
    <div className="flex flex-col gap-3">
      <button className={classNames(buttonClassName, "button-primary")}>
        {/* Content */}
      </button>
      <button className={classNames(buttonClassName, "button-secondary")}>
        {/* Content */}
      </button>
    </div>
  );
};
只有当类集合被同一组件中的多个元素复用,或者重复的类列表明显影响可读性时,才将其提取为变量。不要提取仅使用一次的类集合。
当多个元素共享相同的基础类时,提取到变量中:
tsx
const Buttons = () => {
  const buttonClassName = classNames(
    "flex w-full shrink-0 items-center justify-center transition-colors",
    "lg:cursor-pointer",
  );

  return (
    <div className="flex flex-col gap-3">
      <button className={classNames(buttonClassName, "button-primary")}>
        {/* Content */}
      </button>
      <button className={classNames(buttonClassName, "button-secondary")}>
        {/* Content */}
      </button>
    </div>
  );
};

5. Quick Checklist

5. 快速检查清单

  • classNames
    imported from
    'classnames'
  • className?: string
    prop is optional
  • Consumer
    className
    merged at the end
  • Breakpoints ordered mobile-first (base → xs → sm → md → lg → xl → 2xl)
  • Each breakpoint in its own string argument
  • Utilities ordered: layout → spacing → typography → colors → effects
  • No trailing comma after last argument
  • Reused class sets extracted to a variable
  • classNames
    'classnames'
    导入
  • className?: string
    属性为可选
  • 使用者传入的
    className
    最后合并
  • 断点按移动端优先顺序排列(base → xs → sm → md → lg → xl → 2xl)
  • 每个断点单独作为一个字符串参数
  • 工具类排序:布局 → 间距 → 排版 → 颜色 → 特效
  • 最后一个参数后无尾随逗号
  • 复用的类集合已提取为变量