定制组件

了解如何使用Tailwind Variants API自定义Nuxt UI组件,以实现高级、灵活和可维护的样式。

Tailwind变体

Nuxt UI组件使用Tailwind Variants API进行样式化,该API提供了一种强大的方法来创建变体和管理组件样式。

老虎机

组件可以有多个slots,每个slots代表组件中不同的HTML元素或部分。这些插槽允许灵活的内容插入和样式。

我们以Card组件为例,它有多个插槽:

export default {
  slots: {
    root: 'rounded-lg overflow-hidden',
    header: 'p-4 sm:px-6',
    title: 'text-highlighted font-semibold',
    description: 'mt-1 text-muted text-sm',
    body: 'p-4 sm:p-6',
    footer: 'p-4 sm:px-6'
  }
}
上面的片段是经过删节的。真实的组件在每个插槽上也带有v-if保护,主题在插槽顶部定义了variant样式。

有些组件没有插槽,它们只是由一个根元素组成。在这种情况下,主题只定义了base插槽,例如Container组件:

export default {
  base: 'w-full max-w-(--ui-container) mx-auto px-4 sm:px-6 lg:px-8'
}
没有插槽的组件仍然有一个ui prop。它公开了一个base键,而不是每个插槽一个键。Link和Icon是例外:它们没有ui prop,所以使用class prop来覆盖它们的样式。

变体

组件支持variants,可以根据组件的props动态调整不同slots的样式。

例如,Avatar组件使用size变体来控制其外观:

src/theme/avatar.ts
export default {
  slots: {
    root: 'inline-flex items-center justify-center shrink-0 select-none overflow-hidden rounded-full align-middle bg-elevated',
    image: 'h-full w-full rounded-[inherit] object-cover'
  },
  variants: {
    size: {
      sm: {
        root: 'size-7 text-sm'
      },
      md: {
        root: 'size-8 text-base'
      },
      lg: {
        root: 'size-9 text-lg'
      }
    }
  },
  defaultVariants: {
    size: 'md'
  }
}

这样,size prop会将相应的样式应用到root插槽:

<template>
  <UAvatar src="https://github.com/nuxt.png" size="lg" />
</template>

默认变体

defaultVariants属性在没有传递prop时为每个变量设置默认值。

例如,Avatar组件的默认大小设置为md:

src/theme/avatar.ts
export default {
  slots: {
    root: 'inline-flex items-center justify-center shrink-0 select-none overflow-hidden rounded-full align-middle bg-elevated',
    image: 'h-full w-full rounded-[inherit] object-cover'
  },
  variants: {
    size: {
      sm: {
        root: 'size-7 text-sm'
      },
      md: {
        root: 'size-8 text-base'
      },
      lg: {
        root: 'size-9 text-lg'
      }
    }
  },
  defaultVariants: {
    size: 'md'
  }
}
您可以在nuxt.config.ts中使用theme.defaultVariants选项来覆盖使用md和primary的每个组件的默认size和color。
您可以在vite.config.ts中使用theme.defaultVariants选项来覆盖使用md和primary的每个组件的默认size和color。

复合变体

某些组件使用compoundVariants属性在同时满足多个变量条件时应用类。

例如,Button组件使用compoundVariants属性为特定的color和variant组合应用类:

src/theme/button.ts
import type { ModuleOptions } from '../module'

export default (options: Required<ModuleOptions>) => ({
  slots: {
    base: ['rounded-md font-medium inline-flex items-center disabled:cursor-not-allowed aria-disabled:cursor-not-allowed disabled:opacity-75 aria-disabled:opacity-75', options.theme.transitions && 'transition-colors']
  },
  variants: {
    color: {
      ...Object.fromEntries((options.theme.colors || []).map((color: string) => [color, ''])),
      neutral: ''
    },
    variant: {
      solid: '',
      outline: '',
      soft: '',
      subtle: '',
      ghost: '',
      link: ''
    }
  },
  compoundVariants: [
    ...(options.theme.colors || []).map((color: string) => ({
      color,
      variant: 'outline',
      class: `ring ring-inset ring-${color}/50 text-${color} hover:bg-${color}/10 active:bg-${color}/10 disabled:bg-transparent aria-disabled:bg-transparent dark:disabled:bg-transparent dark:aria-disabled:bg-transparent outline-${color}/25 focus-visible:outline-3 focus-visible:ring-${color}`
    })),
    {
      color: 'neutral',
      variant: 'outline',
      class: 'ring ring-inset ring-accented text-default bg-default hover:bg-elevated active:bg-elevated disabled:bg-default aria-disabled:bg-default outline-inverted/25 focus-visible:outline-3 focus-visible:ring-inverted'
    }
  ],
  defaultVariants: {
    color: 'primary',
    variant: 'solid'
  }
})

自定义主题

您有多种方式来自定义Nuxt UI组件的外观,您可以一次为所有组件或每个组件进行自定义。

Tailwind Variants在后台使用tailwind-merge来合并类,因此您不必担心类冲突。
您可以通过两种方式探索每个组件的主题:
  • 检查每个组件文档中的Theme部分。
  • 直接在GitHub存储库src/theme中浏览源代码。

全局配置

您可以使用与主题对象完全相同的结构,在app.config.ts中全局覆盖组件的主题。

您可以使用与主题对象完全相同的结构,在vite.config.ts中全局覆盖组件的主题。

您可以自定义组件的slots、variants、compoundVariants和defaultVariants,以更改组件的默认主题:

app/app.config.ts
export default defineAppConfig({
  ui: {
    button: {
      slots: {
        base: 'font-bold'
      },
      variants: {
        size: {
          md: {
            leadingIcon: 'size-4'
          }
        }
      },
      compoundVariants: [{
        color: 'neutral',
        variant: 'outline',
        class: 'ring-default hover:bg-accented'
      }],
      defaultVariants: {
        color: 'neutral',
        variant: 'outline'
      }
    }
  }
})
vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import ui from '@nuxt/ui/vite'

export default defineConfig({
  plugins: [
    vue(),
    ui({
      ui: {
        button: {
          slots: {
            base: 'font-bold'
          },
          variants: {
            size: {
              md: {
                leadingIcon: 'size-4'
              }
            }
          },
          compoundVariants: [{
            color: 'neutral',
            variant: 'outline',
            class: 'ring-default hover:bg-accented'
          }],
          defaultVariants: {
            color: 'neutral',
            variant: 'outline'
          }
        }
      }
    })
  ]
})
在本例中,font-bold覆盖所有按钮上的font-medium,size-4覆盖size-5类(当size="md"时),ring-default hover:bg-accented覆盖ring-accented hover:bg-elevated(当color="neutral"和variant="outline"时)。按钮现在默认为color="neutral"和variant="outline"。

默认情况下,这些类合并到组件的默认值中。您也可以将插槽设置为函数来替换它们。它将插槽的默认类作为其参数接收,因此您可以重用其中的一部分。来自variants和compoundVariants的类仍然在顶部应用,就像您传递字符串时一样。

app/app.config.ts
export default defineAppConfig({
  ui: {
    button: {
      slots: {
        label: () => 'text-base font-bold'
      }
    }
  }
})
vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import ui from '@nuxt/ui/vite'

export default defineConfig({
  plugins: [
    vue(),
    ui({
      ui: {
        button: {
          slots: {
            label: () => 'text-base font-bold'
          }
        }
      }
    })
  ]
})
只在一个地方设置一个slot到一个函数。Nuxt将每个layer的app.config.ts与defuFn合并,这将使用该键已经具有的任何值调用您的函数,并且只保留结果。Nuxt UI然后读取一个普通字符串,因此其类将合并到默认值中,而不是替换它们。
要一次性从所有组件中删除默认类,请在nuxt.config.ts中使用theme.unstyled选项。
要一次性从所有组件中删除默认类,请在vite.config.ts中使用theme.unstyled选项。

Theme组件

Theme组件覆盖所有子组件的插槽和属性默认值,而不影响应用的其余部分。这优先于全局配置,但是ui和class仍然战胜了它。插槽接受相同的函数形式来替换它们的类而不是合并。它在与ui相同的点上解析prop,所以它的行为相同。

<template>
  <UTheme
    :ui="{
      button: {
        base: 'rounded-full'
      }
    }"
  >
    <div class="flex items-center gap-2">
      <UButton label="Button" color="neutral" />
      <UButton label="Button" color="neutral" variant="outline" />
      <UButton label="Button" color="neutral" variant="subtle" />
    </div>
  </UTheme>
</template>

ui道具

您也可以使用ui属性覆盖组件的slots。这优先于全局配置和解析的variants。

<template>
  <UButton
    trailing-icon="i-lucide-chevron-right"
    size="md"
    color="neutral"
    variant="outline"
    :ui="{
      trailingIcon: 'rotate-90 size-3'
    }"
  >
    Button
  </UButton>
</template>
在本例中,trailingIcon插槽被size-3覆盖,即使md大小变体将向其应用size-5类。

槽值也可以是替换其类而不是合并它们的函数:

<template>
  <UButton :ui="{ label: () => 'text-base font-bold' }" label="Button" />
</template>
ui prop在variants之后解析,因此与全局配置不同,这里的函数替换了插槽的resolved类。其参数包含variants和compoundVariants匹配的内容。同一插槽上的class prop仍然合并在返回的类之上。

class道具

使用class属性覆盖root或base插槽的类。这优先于全局配置和解析的variants。

<template>
  <UButton class="font-bold rounded-full">Button</UButton>
</template>
在本例中,font-bold类将覆盖此按钮上的默认font-medium类。