样式模式

从各种预设主题中选择,或者开发自己的主题。

PrimeVue 是一个与设计无关的库,因此与其他一些 UI 库不同,它不会强制执行特定的样式,例如材料设计。样式使用主题与组件分离。主题包含两个部分:*基础* 和 *预设*。基础是使用 CSS 变量作为占位符的样式规则,而预设是一组设计令牌,用于通过将令牌映射到 CSS 变量来馈送基础。基础可以配置不同的预设,目前,光环、拉拉和诺拉是可用的预设,在即将推出的版本中将提供更多预设,例如 Material Design。

Architecture

样式模式架构的核心是基于名为 *设计令牌* 的概念,预设在 3 个层级定义令牌配置:*原始*、*语义* 和 *组件*。

原始令牌

原始令牌没有上下文,颜色调色板是原始令牌的一个很好的例子,例如 *blue-50* 到 *blue-900*。名为 *blue-500* 的令牌可以用作主要颜色,消息的背景,但就其本身而言,令牌的名称不会指示上下文。通常,它们由语义令牌使用。

语义令牌

语义令牌定义内容,它们的名称指示它们的使用位置,*primary.color* 是语义令牌的一个众所周知的例子。语义令牌映射到原始令牌或其他语义令牌。*colorScheme* 令牌组是一个特殊的变量,用于根据应用程序中激活的颜色方案定义令牌,这允许根据颜色方案(如深色模式)定义不同的令牌。

组件令牌

组件令牌是每个组件的独立令牌,例如 *inputtext.background* 或 *button.color*,它们映射到语义令牌。例如,*button.background* 组件令牌映射到 *primary.color* 语义令牌,该令牌映射到 *green.500* 原始令牌。

最佳实践

在定义核心调色板时使用原始令牌,并使用语义令牌指定通用设计元素,例如焦点环、主要颜色和表面。组件令牌仅应在自定义特定组件时使用。通过将您自己的设计令牌定义为自定义预设,您将能够定义自己的样式,而无需接触 CSS。使用样式类覆盖 PrimeVue 组件不是最佳实践,应该是最后的手段,设计令牌是建议的方法。

*theme* 属性用于自定义初始主题。


import PrimeVue from 'primevue/config';
import Aura from '@primevue/themes/aura';

const app = createApp(App);

app.use(PrimeVue, {
    // Default theme configuration
    theme: {
        preset: Aura,
        options: {
            prefix: 'p',
            darkModeSelector: 'system',
            cssLayer: false
        }
    }
 });

*options* 属性定义了如何从预设的设计令牌生成 CSS。

前缀

CSS 变量的前缀,默认为 *p*。例如,*primary.color* 设计令牌将是 *var(--p-primary-color)*。


options: {
    prefix: 'my'
}

darkModeSelector

用于封装深色模式 CSS 变量的 CSS 规则,默认值为 *system*,用于生成 *@media (prefers-color-scheme: dark)*。如果您需要根据用户选择使深色模式可切换,请定义一个类选择器,例如 *。app-dark*,并在文档根目录切换此类。请参阅深色模式切换部分以获取示例。


options: {
    darkModeSelector: '.my-app-dark'
}

cssLayer

定义样式是否应在 CSS 层 内定义(默认情况下名为 *primeui*)或不定义。CSS 层将有助于声明自定义级联层,以便于自定义。默认值为 *false*。


options: {
    cssLayer: {
        name: 'primevue',
        order: 'tailwind-base, primevue, tailwind-utilities'
    }
}

目前,光环、拉拉和诺拉是可用的预设,一个基于 Material Design 的新预设计划于 2024 年底发布。

令牌使用点分隔符描述,例如 *primary.color*、*form.field.background* 或 *checkbox.icon.checked.color*。在预设配置中,在映射点分隔符时使用驼峰式命名法和对象属性。以下是如何表示 *checkbox.icon.checked.color* 的复选框组件令牌的示例,所有替代方案的结果都相同。


export default {
    iconCheckedColor: //...,
}

export default {
    icon: {
        checkedColor: //...
    }
}

export default {
    icon: {
        checked: {
            color: //...
        }
    }
}

以下键在预设方案中被保留,不能用作令牌名称:*primitive*、*semantic*、*components*、*directives*、*colorscheme*、*light*、*dark*、*common*、*root*、*states*。

*definePreset* 实用程序用于在 PrimeVue 设置期间自定义现有预设。第一个参数是要自定义的预设,第二个参数是要覆盖的设计令牌。


import PrimeVue from 'primevue/config';
import { definePreset } from '@primevue/themes';
import Aura from '@primevue/themes/aura';

const MyPreset = definePreset(Aura, {
    //Your customizations, see the following sections for examples
});

app.use(PrimeVue, {
    theme: {
        preset: MyPreset
    }
 });

*primary* 定义主要调色板,默认值映射到 *emerald* 原始令牌。让我们进行设置以使用 *indigo* 代替。


const MyPreset = definePreset(Aura, {
    semantic: {
        primary: {
            50: '{indigo.50}',
            100: '{indigo.100}',
            200: '{indigo.200}',
            300: '{indigo.300}',
            400: '{indigo.400}',
            500: '{indigo.500}',
            600: '{indigo.600}',
            700: '{indigo.700}',
            800: '{indigo.800}',
            900: '{indigo.900}',
            950: '{indigo.950}'
        }
    }
});

*noir* 模式是使用黑色色调作为主要色调的变体的昵称,它需要额外的 *colorScheme* 配置来实现。使用黑色和白色变体作为主要颜色的样本预设配置;


const Noir = definePreset(Aura, {
    semantic: {
        primary: {
            50: '{zinc.50}',
            100: '{zinc.100}',
            200: '{zinc.200}',
            300: '{zinc.300}',
            400: '{zinc.400}',
            500: '{zinc.500}',
            600: '{zinc.600}',
            700: '{zinc.700}',
            800: '{zinc.800}',
            900: '{zinc.900}',
            950: '{zinc.950}'
        },
        colorScheme: {
            light: {
                primary: {
                    color: '{zinc.950}',
                    inverseColor: '#ffffff',
                    hoverColor: '{zinc.900}',
                    activeColor: '{zinc.800}'
                },
                highlight: {
                    background: '{zinc.950}',
                    focusBackground: '{zinc.700}',
                    color: '#ffffff',
                    focusColor: '#ffffff'
                }
            },
            dark: {
                primary: {
                    color: '{zinc.50}',
                    inverseColor: '{zinc.950}',
                    hoverColor: '{zinc.100}',
                    activeColor: '{zinc.200}'
                },
                highlight: {
                    background: 'rgba(250, 250, 250, .16)',
                    focusBackground: 'rgba(250, 250, 250, .24)',
                    color: 'rgba(255,255,255,.87)',
                    focusColor: 'rgba(255,255,255,.87)'
                }
            }
        }
    }
});

颜色方案调色板在亮模式和暗模式之间变化,使用表面令牌指定。以下示例使用 *zinc* 作为亮模式,*slategray* 作为暗模式。使用此设置,亮模式将具有灰度色调,而暗模式将包含蓝色色调。


const MyPreset = definePreset(Aura, {
    semantic: {
        colorScheme: {
            light: {
                surface: {
                    0: '#ffffff',
                    50: '{zinc.50}',
                    100: '{zinc.100}',
                    200: '{zinc.200}',
                    300: '{zinc.300}',
                    400: '{zinc.400}',
                    500: '{zinc.500}',
                    600: '{zinc.600}',
                    700: '{zinc.700}',
                    800: '{zinc.800}',
                    900: '{zinc.900}',
                    950: '{zinc.950}'
                }
            },
            dark: {
                surface: {
                    0: '#ffffff',
                    50: '{slate.50}',
                    100: '{slate.100}',
                    200: '{slate.200}',
                    300: '{slate.300}',
                    400: '{slate.400}',
                    500: '{slate.500}',
                    600: '{slate.600}',
                    700: '{slate.700}',
                    800: '{slate.800}',
                    900: '{slate.900}',
                    950: '{slate.950}'
                }
            }
        }
    }
});

没有字体设计,因为 UI 组件继承其字体设置来自应用程序。

表单输入组件的设计令牌源自 *form.field* 令牌组。此自定义示例将悬停时的边框颜色更改为主要颜色。任何依赖此语义令牌的组件,例如 *dropdown.hover.border.color* 和 *textarea.hover.border.color* 都将接收此更改。


const MyPreset = definePreset(Aura, {
    semantic: {
        colorScheme: {
            light: {
                formField: {
                    hoverBorderColor: '{primary.color}'
                }
            },
            dark: {
                formField: {
                    hoverBorderColor: '{primary.color}'
                }
            }
        }
    }
});

焦点环定义轮廓宽度、样式、颜色和偏移量。让我们使用更厚的轮廓,使用主要颜色作为轮廓。


const MyPreset = definePreset(Aura, {
    semantic: {
        focusRing: {
            width: '2px',
            style: 'dashed',
            color: '{primary.color}',
            offset: '1px'
        }
    }
});

特定组件的设计令牌在 *components* 层定义。如果您正在构建自己的样式,则不建议覆盖组件令牌,而是应该优先构建自己的预设。此配置是全局的,适用于所有卡片组件,如果您需要在页面上本地自定义特定组件,请查看“作用域 CSS”部分以获取示例。


const MyPreset = definePreset(Aura, {
    components: {
        card: {
            colorScheme: {
                light: {
                    root: {
                        background: '{surface.0}',
                        color: '{surface.700}'
                    },
                    subtitle: {
                        color: '{surface.500}'
                    }
                },
                dark: {
                    root: {
                        background: '{surface.900}',
                        color: '{surface.0}'
                    },
                    subtitle: {
                        color: '{surface.400}'
                    }
                }
            }
        }
    }
});

设计令牌可以使用 CSS 变量作用域到特定组件。在此示例中,第一个开关使用全局令牌,而第二个开关使用自己的令牌覆盖全局令牌。


<template>
    <div>
        <ToggleSwitch v-model="checked1" />
        <ToggleSwitch v-model="checked2" :dt="amberSwitch" />
    </div>
</template>

<script setup>
import { ref } from 'vue';

const checked1 = ref(true);
const checked2 = ref(true);
const amberSwitch = ref({
    handle: {
        borderRadius: '4px'
    },
    colorScheme: {
        light: {
            root: {
                checkedBackground: '{amber.500}',
                checkedHoverBackground: '{amber.600}',
                borderRadius: '4px'
            },
            handle: {
                checkedBackground: '{amber.50}',
                checkedHoverBackground: '{amber.100}'
            }
        },
        dark: {
            root: {
                checkedBackground: '{amber.400}',
                checkedHoverBackground: '{amber.300}',
                borderRadius: '4px'
            },
            handle: {
                checkedBackground: '{amber.900}',
                checkedHoverBackground: '{amber.800}'
            }
        }
    }
});
</script>


完全替换当前预设,常见的用例是在运行时动态更改预设。


import { usePreset } from '@primevue/themes';

const onButtonClick() {
    usePreset(MyPreset);
}

将提供的令牌合并到当前预设中,例如动态更改主要调色板。


import { updatePreset } from '@primevue/themes';

const changePrimaryColor() {
    updatePreset({
        semantic: {
            primary: {
                50: '{indigo.50}',
                100: '{indigo.100}',
                200: '{indigo.200}',
                300: '{indigo.300}',
                400: '{indigo.400}',
                500: '{indigo.500}',
                600: '{indigo.600}',
                700: '{indigo.700}',
                800: '{indigo.800}',
                900: '{indigo.900}',
                950: '{indigo.950}'
            }
        }
    })
}

更新主要颜色,这是使用 *updatePreset* 执行相同更新的简写方法。


import { updatePrimaryPalette } from '@primevue/themes';

const changePrimaryColor() {
    updatePrimaryPalette({
        50: '{indigo.50}',
        100: '{indigo.100}',
        200: '{indigo.200}',
        300: '{indigo.300}',
        400: '{indigo.400}',
        500: '{indigo.500}',
        600: '{indigo.600}',
        700: '{indigo.700}',
        800: '{indigo.800}',
        900: '{indigo.900}',
        950: '{indigo.950}'
    });
}

更新表面颜色,这是使用 *updatePreset* 执行相同更新的简写方法。


import { updateSurfacePalette } from '@primevue/themes';

const changeSurfaces() {
    //changes surfaces both in light and dark mode
    updateSurfacePalette({
        50: '{zinc.50}',
        // ...
        950: '{zinc.950}'
    });
}

const changeLightSurfaces() {
    //changes surfaces only in light
    updateSurfacePalette({
        light: {
            50: '{zinc.50}',
            // ...
            950: '{zinc.950}'
        }
    });
}

const changeDarkSurfaces() {
    //changes surfaces only in dark mode
    updateSurfacePalette({
        dark: {
            50: '{zinc.50}',
            // ...
            950: '{zinc.950}'
        }
    });
}

*dt* 函数返回有关令牌的信息,如完整路径和值。如果您需要以编程方式访问令牌,这将很有用。


import { $dt } from '@primevue/themes';

const duration = $dt('transition.duration');
/*
    duration: {
        name: '--transition-duration',
        variable: 'var(--p-transition-duration)',
        value: '0.2s'
    }
*/

const primaryColor = $dt('primary.color');
/*
    primaryColor: {
        name: '--primary-color',
        variable: 'var(--p-primary-color)',
        value: {
        light: {
            value: '#10b981',
            paths: {
                name: 'semantic.primary.color',
                binding: {
                    name: 'primitive.emerald.500'
                }
            }
        },
        dark: {
            value: '#34d399',
            paths: {
                name: 'semantic.primary.color',
                binding: {
                    name: 'primitive.emerald.400'
                }
            }
        }
    }
}
*/

返回给定颜色的 50 到 950 的色调和色度,作为对象。


import { palette } from '@primevue/themes';

// custom color
const values1 = palette('#10b981');

// copy an existing token set
const primaryColor = palette('{blue}');

预设的调色板由 *primitive* 设计令牌组定义。默认颜色源自 Tailwind 颜色,并有一些扩展,使其与无样式模式的 Tailwind Presets 项目保持一致。

颜色可以在 CSS 中作为变量访问,也可以使用 *dt* 实用程序以编程方式访问。


// With CSS
var(--p-blue-500)

// With JS
$dt('blue.500').value

  • 翡翠
    50
    100
    200
    300
    400
    500
    600
    700
    800
    900
    950
  • 绿色
    50
    100
    200
    300
    400
    500
    600
    700
    800
    900
    950
  • 青柠
    50
    100
    200
    300
    400
    500
    600
    700
    800
    900
    950
  • 红色
    50
    100
    200
    300
    400
    500
    600
    700
    800
    900
    950
  • 橙色
    50
    100
    200
    300
    400
    500
    600
    700
    800
    900
    950
  • 琥珀色
    50
    100
    200
    300
    400
    500
    600
    700
    800
    900
    950
  • 黄色
    50
    100
    200
    300
    400
    500
    600
    700
    800
    900
    950
  • 青绿色
    50
    100
    200
    300
    400
    500
    600
    700
    800
    900
    950
  • 青色
    50
    100
    200
    300
    400
    500
    600
    700
    800
    900
    950
  • 天蓝色
    50
    100
    200
    300
    400
    500
    600
    700
    800
    900
    950
  • 蓝色
    50
    100
    200
    300
    400
    500
    600
    700
    800
    900
    950
  • 靛蓝色
    50
    100
    200
    300
    400
    500
    600
    700
    800
    900
    950
  • 紫色
    50
    100
    200
    300
    400
    500
    600
    700
    800
    900
    950
  • 紫罗兰色
    50
    100
    200
    300
    400
    500
    600
    700
    800
    900
    950
  • 洋红色
    50
    100
    200
    300
    400
    500
    600
    700
    800
    900
    950
  • 粉色
    50
    100
    200
    300
    400
    500
    600
    700
    800
    900
    950
  • 玫瑰色
    50
    100
    200
    300
    400
    500
    600
    700
    800
    900
    950
  • 石板色
    50
    100
    200
    300
    400
    500
    600
    700
    800
    900
    950
  • 灰色
    50
    100
    200
    300
    400
    500
    600
    700
    800
    900
    950
  • 锌灰色
    50
    100
    200
    300
    400
    500
    600
    700
    800
    900
    950
  • 中性色
    50
    100
    200
    300
    400
    500
    600
    700
    800
    900
    950
  • 石色
    50
    100
    200
    300
    400
    500
    600
    700
    800
    900
    950

PrimeVue 在主题配置中使用系统作为默认的darkModeSelector。如果您在应用程序中有一个暗黑模式切换按钮,请将darkModeSelector设置为使用的选择器,例如.my-app-dark,以便 PrimeVue 可以与您的明暗模式切换按钮无缝衔接。


import PrimeVue from 'primevue/config';
import Aura from '@primevue/themes/aura';

const app = createApp(App);

app.use(PrimeVue, {
    // Default theme configuration
    theme: {
        preset: Aura,
        options: {
            darkModeSelector: '.my-app-dark',
        }
    }
 });

以下是一个非常基本的暗黑模式切换按钮示例实现,您可以通过使用prefers-color-scheme从系统最初获取颜色方案并使用localStorage使其成为状态化的,来进一步扩展它。有关更多信息,请查看这篇文章 文章


<Button label="Toggle Dark Mode" @click="toggleDarkMode()" />


function toggleDarkMode() {
    const element = document.querySelector('html');
    element.classList.toggle('my-app-dark');
}

如果您希望始终使用暗黑模式,请首先应用darkModeSelector,并且不要更改它。

PrimeVue CSS 层仅在主题配置中显式启用分层时才应用于样式化模式。在非样式化模式下,内置的 CSS 类不包括在内,因此不需要任何层。

@layer 是一个标准的 CSS 功能,用于定义自定义优先级顺序的级联层。如果您需要更熟悉层,请访问 MDN 文档以开始学习。在样式化模式下,当在主题配置中启用cssLayer 选项时,PrimeVue 会将内置的样式类包装在primevue 级联层下,使库样式易于覆盖。您应用程序中的 CSS 没有任何层,具有最高的 CSS 特异性,因此您将能够覆盖样式,无论位置或类名如何强。

例如,假设您需要删除正在使用的主题中定义的 ToggleSwitch 组件的圆角边框。为了实现这一点,需要覆盖.p-toggleswitch .p-toggleswitch-slider 选择器。如果没有层,我们将不得不编写更强的 CSS 或使用!important,但是,使用层,这不会出现问题,因为您的 CSS 可以始终通过更简单的类名(例如my-switch-slider)覆盖 PrimeVue。这种方法的另一个优点是,它不会强迫您找出组件的内置类名。


<template>
    <ToggleSwitch v-model="checked" :pt="{ slider: 'my-switch-slider' }" />
</template>

<script>
import { ref } from "vue";

const checked = ref(false);
</script>

<style>
.my-switch-slider {
    border-radius: 0;
}

.my-switch-slider:before {
    border-radius: 0;
}
</style>

层也使使用 CSS 模块变得更容易,查看 CSS 模块指南以获取示例。

如果您在 HTML 元素(如输入和按钮)上具有全局样式,而这些样式也被 PrimeVue 使用,则自定义的简便性可能会出现问题,因为范围更广的全局样式(例如button { } 且没有层)始终会覆盖 PrimeVue 组件,导致意外的结果。全局样式应用于标准 HTML 元素的一个常见用例是 CSS 重置实用程序,用于删除浏览器的默认样式。在这种情况下,最佳实践是在层中包装您的 CSS,例如reset,并确保primevue 在您的层之后,因为之后定义的层具有更高的优先级。这样,您的重置 CSS 不会妨碍 PrimeVue 组件。


/* Order */
@layer reset, primevue;

/* Reset CSS */
@layer reset {
    button,
    input {
        /* CSS to Reset */
    }
}

流行 CSS 库的示例层配置。

Bootstrap

Bootstrap 有一个reboot 实用程序来重置标准元素的 CSS。如果您包含此实用程序,您可以在导入时为其提供一个层。


@layer bootstrap-reboot, primevue;

@import "bootstrap-reboot.css" layer(bootstrap-rebooot);

Tailwind

Tailwind CSS 在 base 中包含一个重置实用程序,称为 preflight。如果您使用此功能,请将 base 和实用程序包装在单独的层中,并确保 primevue 层位于 base 之后。


@layer tailwind-base, primevue, tailwind-utilities;

@layer tailwind-base {
    @tailwind base;
}

@layer tailwind-utilities {
    @tailwind components;
    @tailwind utilities;
}

Normalize

Normalize 是另一个用于重置标准元素 CSS 的实用程序。在导入 CSS 文件时,将其分配给一个层,并使用 primevue 在归一化层之后定义层顺序。


@layer normalize, primevue;

@import "normalize.css" layer(normalize-reset);

CSS 模块 通过在您的 SFC 中的样式元素上启用module 属性来支持。使用$style 关键字将类应用于 PrimeVue 组件。建议在使用 CSS 模块时启用cssLayer,以便 PrimeVue 样式具有较低的 CSS 特异性。


<style module>
.myinput {
    border-radius: 2rem;
    padding: 1rem 2rem;
    border-width: 2px;
}
</style>


<template>
    <InputText :class="$style.myinput" placeholder="Search" />
</template>

PrimeVue UI 组件使用rem 单位,1rem 等于html 元素的字体大小,默认情况下为16px。使用根字体大小全局调整组件的大小。本网站使用14px 作为基准,因此如果您的基准字体大小不同,它可能与您的应用程序不同。


html {
    font-size: 14px;
}