在本指南中,我们将介绍如何为您的表单和输入框创建一个自定义 Tailwind 主题。Tailwind 已经成为 CSS 实用类库的最前沿,而 FormKit 是考虑到其功能而编写的。让我们开始吧!
本指南假设您正在使用一个标准的 Vue 3 构建工具,如 Vite、Nuxt 3 或 Vue CLI,它们允许您导入 .vue
单文件组件。
如果您计划为表单样式使用 Tailwind CSS,请确保您的项目不导入随 FormKit 附带的基本 genesis
主题 — 否则您将获得奇怪的样式结果。
在表示组件的 .vue
文件的上下文中,可以使用 FormKit 提供的 section-key
类属性或 classes
属性创建一个 Tailwind 主题。
如果您的组件代表您的整个表单,并且您的项目只需要一个表单,那么这可能就是您所需要的。下面是一个示例,展示了如何使用 section-key
属性和 classes
属性将相同的 Tailwind 类应用于 FormKit text
输入框:
这是一种低成本的方法,可以将 Tailwind 样式应用于您的 FormKit 表单,但如果您有多个表单怎么办?在组件之间复制粘贴类列表并不理想,而且会导致您的项目随着时间的推移在样式上出现无意识的变化。
让我们探讨一下如何将 Tailwind 类全局应用于我们项目中的 所有 FormKit 输入框。
FormKit 附带了一个名为 @formkit/themes
的第一方包,其中包含了 Tailwind CSS 支持 —— 这使得在您的项目中为 FormKit 创建一个 Tailwind CSS 主题变得非常容易。
该包允许您将主题作为一个 JavaScript 对象编写,按输入 type
和 sectionKey
分组。此外,您还可以访问一些基于输入和表单状态的 Tailwind 变体,如 formkit-invalid:
和 formkit-disabled:
,这些变体允许您动态更改输入样式。
首先,我们需要将该包添加到我们的项目中。
npm install @formkit/themes
接下来我们需要做两件事:
@formkit/themes
中的 formKitTailwind
插件添加到我们项目的 tailwind.config.js
文件中。@formkit/themes
中导入 generateClasses
辅助函数,并在定义 FormKit 配置选项的地方使用它。// tailwind.config.js
const formKitTailwind = require('@formkit/themes/tailwindcss');
module.exports {
...
plugins: [
formKitTailwind
]
...
}
// app.js
import { createApp } from 'vue'
import App from './App.vue'
import { plugin, defaultConfig } from '@formkit/vue'
import { generateClasses } from '@formkit/themes'
import '../dist/index.css' // 您的 Tailwind 样式所在的地方
createApp(App)
.use(
plugin,
defaultConfig({
config: {
classes: generateClasses({
// 我们的主题将放在这里。
// ...
// text: {
// label: 'font-bold text-gray-300',
// ...
// }
// ...
}),
},
})
)
.mount('#app')
如果您使用的是单个文件进行配置,如 Nuxt 中的 formkit.config.js
,而不是 app.js
,您需要在 tailwind.config.js
中的 content
属性中添加该文件的路径:
// Nuxt 用户的 tailwind.config.js
const formKitTailwind = require('@formkit/themes/tailwindcss')
export default {
// 添加 formkit.config.js 文件
content: ['./src/**/*.{html,js}', './path/to/formkit.config.js'],
plugins: [formKitTailwind],
}
完成此设置后,我们就可以开始编写我们的 Tailwind 主题了!
首先,让我们为 text
样式输入应用一些合理的类。这将覆盖很大的表面积,因为我们可以轻松地将这些样式重用到其他类似文本的输入,如 email
、password
、date
等。
为了专门针对 text
输入,我们将在主题对象中创建一个 text
键,然后根据需要将类应用到每个 sectionKey
。
这是一个应用了 Tailwind 类的 text
输入:
@formkit/themes
中的 formKitTailwind
插件提供了许多变体,您可以在类列表中使用这些变体来动态响应输入和表单状态。
如果您在嵌套情况下使用变体,变体可能与其父级而不是其自身相关联。
要解决这个问题,在外部部分添加 group/{modifier}
,并使用相同的修饰符 formkit-invalid/{modifier}:
当前提供的变体有:
formkit-disabled:
formkit-invalid:
formkit-checked:
formkit-errors:
formkit-complete:
formkit-loading:
formkit-submitted:
formkit-multiple:
formkit-prefix-icon
formkit-suffix-icon
您可以像使用内置的 Tailwind 变体(如 dark:
和 hover:
)一样使用这些变体。
让我们为 formkit-invalid
和 formkit-disabled
添加一些变体到我们的文本输入:
现在我们开始做了!要创建一个全面的主题,我们只需要为项目中使用的所有其他输入类型的 sectionKeys
定义类列表。
不过我们可以做一些改进。@formkit/themes
中的 generateClasses
辅助函数允许使用一个特殊的 global
键,该键将应用于 所有 输入。这对于像 help
和 messages
这样的 sectionKeys
很有帮助,因为它们通常在项目中的所有输入类型上都具有相同的样式。
通过在主题对象中使用 global
和 family:
键,您可以将类列表应用于具有给定 sectionKey
的 所有 输入,无论是全局还是在输入家族内。这对于像标签或帮助文本这样的东西很有用,当您想在各种各样的输入中共享样式时。
让我们创建一个 "Kitchen Sink" 的输入类型,每个输入类型都有其定义的类列表。为了更好地阅读,这里是独立的主题:
// The following Tailwind theme aspires to be a reproduction of the
// default optional Genesis CSS theme that ships with FormKit
export default {
// Global styles apply to _all_ inputs with matching section keys
global: {
fieldset: 'max-w-md border border-gray-400 rounded px-2 pb-1',
help: 'text-xs text-gray-500',
inner: 'formkit-disabled:bg-gray-200 formkit-disabled:cursor-not-allowed formkit-disabled:pointer-events-none',
input: 'appearance-none bg-transparent focus:outline-none focus:ring-0 focus:shadow-none',
label: 'block mb-1 font-bold text-sm',
legend: 'font-bold text-sm',
loaderIcon: 'inline-flex items-center w-4 text-gray-600 animate-spin',
message: 'text-red-500 mb-1 text-xs',
messages: 'list-none p-0 mt-1 mb-0',
outer: 'mb-4 formkit-disabled:opacity-50',
prefixIcon: 'w-10 flex self-stretch grow-0 shrink-0 rounded-tl rounded-bl border-r border-gray-400 bg-white bg-gradient-to-b from-transparent to-gray-200 [&>svg]:w-full [&>svg]:max-w-[1em] [&>svg]:max-h-[1em] [&>svg]:m-auto',
suffixIcon: 'w-7 pr-3 flex self-stretch grow-0 shrink-0 [&>svg]:w-full [&>svg]:max-w-[1em] [&>svg]:max-h-[1em] [&>svg]:m-auto'
},
// Family styles apply to all inputs that share a common family
'family:box': {
decorator: 'block relative h-5 w-5 mr-2 rounded bg-white bg-gradient-to-b from-transparent to-gray-200 ring-1 ring-gray-400 peer-checked:ring-blue-500 text-transparent peer-checked:text-blue-500',
decoratorIcon: 'flex p-[3px] w-full h-full absolute top-1/2 left-1/2 -translate-y-1/2 -translate-x-1/2',
help: 'mb-2 mt-1.5',
input: 'absolute w-0 h-0 overflow-hidden opacity-0 pointer-events-none peer',
inner: '$remove:formkit-disabled:bg-gray-200',
label: '$reset text-sm text-gray-700 mt-1 select-none',
wrapper: 'flex items-center mb-1',
},
'family:button': {
input: '$reset inline-flex items-center bg-blue-600 text-white text-sm font-normal py-3 px-6 rounded focus-visible:outline-2 focus-visible:outline-blue-600 focus-visible:outline-offset-2 formkit-disabled:bg-gray-400 formkit-loading:before:w-4 formkit-loading:before:h-4 formkit-loading:before:mr-2 formkit-loading:before:border formkit-loading:before:border-2 formkit-loading:before:border-r-transparent formkit-loading:before:rounded-3xl formkit-loading:before:border-white formkit-loading:before:animate-spin',
wrapper: 'mb-1',
prefixIcon: '$reset block w-4 -ml-2 mr-2 stretch',
suffixIcon: '$reset block w-4 ml-2 stretch',
},
'family:dropdown': {
dropdownWrapper: 'my-2 w-full drop-shadow-lg rounded [&::-webkit-scrollbar]:hidden',
emptyMessageInner: 'flex items-center justify-center text-sm p-2 text-center w-full text-gray-500 [&>span]:mr-3 [&>span]:ml-0',
inner: 'max-w-md relative flex ring-1 ring-gray-400 focus-within:ring-blue-500 focus-within:ring-2 rounded mb-1 formkit-disabled:focus-within:ring-gray-400 formkit-disabled:focus-within:ring-1 [&>span:first-child]:focus-within:text-blue-500',
input: 'w-full px-3 py-2',
listbox: 'bg-white drop-shadow-lg rounded overflow-hidden',
listboxButton: 'flex w-12 self-stretch justify-center mx-auto',
listitem: 'pl-7 relative hover:bg-gray-300 data-[is-active="true"]:bg-gray-300 data-[is-active="true"]:aria-selected:bg-blue-600 aria-selected:bg-blue-600 aria-selected:text-white',
loaderIcon: 'ml-auto',
loadMoreInner: 'flex items-center justify-center text-sm p-2 text-center w-full text-blue-500 formkit-loading:text-gray-500 cursor-pointer [&>span]:mr-3 [&>span]:ml-0',
option: 'p-2.5',
optionLoading: 'text-gray-500',
placeholder: 'p-2.5 text-gray-400',
selector: 'flex w-full justify-between items-center [&u]',
selectedIcon: 'block absolute top-1/2 left-2 w-3 -translate-y-1/2',
selectIcon: 'flex box-content w-4 px-2 self-stretch grow-0 shrink-0',
},
'family:text': {
inner: 'flex items-center max-w-md ring-1 ring-gray-400 focus-within:ring-blue-500 focus-within:ring-2 [&>label:first-child]:focus-within:text-blue-500 rounded mb-1',
input: 'w-full px-3 py-2 border-none text-base text-gray-700 placeholder-gray-400',
},
'family:date': {
inner: 'flex items-center max-w-md ring-1 ring-gray-400 focus-within:ring-blue-500 focus-within:ring-2 [&>label:first-child]:focus-within:text-blue-500 rounded mb-1',
input: 'w-full px-3 py-2 border-none text-gray-700 placeholder-gray-400',
},
// Specific styles apply only to a given input type
color: {
inner: 'flex max-w-[5.5em] w-full formkit-prefix-icon:max-w-[7.5em] formkit-suffix-icon:formkit-prefix-icon:max-w-[10em]',
input: '$reset appearance-none w-full cursor-pointer border-none rounded p-0 m-0 bg-transparent [&::-webkit-color-swatch-wrapper]:p-0 [&::-webkit-color-swatch]:border-none',
suffixIcon: 'min-w-[2.5em] pr-0 pl-0 m-auto'
},
file: {
fileItem: 'flex items-center text-gray-800 mb-1 last:mb-0',
fileItemIcon: 'w-4 mr-2 shrink-0',
fileList: 'shrink grow peer px-3 py-2 formkit-multiple:data-[has-multiple="true"]:mb-6',
fileName: 'break-all grow text-ellipsis',
fileRemove: 'relative z-[2] ml-auto text-[0px] hover:text-red-500 pl-2 peer-data-[has-multiple=true]:text-sm peer-data-[has-multiple=true]:text-blue-500 peer-data-[has-multiple=true]:ml-3 peer-data-[has-multiple=true]:mb-2 formkit-multiple:bottom-[0.15em] formkit-multiple:pl-0 formkit-multiple:ml-0 formkit-multiple:left-[1em] formkit-multiple:formkit-prefix-icon:left-[3.75em]',
fileRemoveIcon: 'block text-base w-3 relative z-[2]',
inner: 'relative max-w-md cursor-pointer formkit-multiple:[&>button]:absolute',
input: 'cursor-pointer text-transparent absolute top-0 right-0 left-0 bottom-0 opacity-0 z-[2]',
noFiles: 'flex w-full items-center px-3 py-2 text-gray-400',
noFilesIcon: 'w-4 mr-2'
},
radio: {
decorator: 'rounded-full',
decoratorIcon: 'w-5 p-[5px]'
},
range: {
inner: '$reset flex items-center max-w-md',
input: '$reset w-full mb-1 h-2 p-0 rounded-full',
prefixIcon: '$reset w-4 mr-1 flex self-stretch grow-0 shrink-0 [&>svg]:max-w-[1em] [&>svg]:max-h-[1em] [&>svg]:m-auto',
suffixIcon: '$reset w-4 ml-1 flex self-stretch grow-0 shrink-0 [&>svg]:max-w-[1em] [&>svg]:max-h-[1em] [&>svg]:m-auto'
},
select: {
inner: 'flex relative max-w-md items-center rounded mb-1 ring-1 ring-gray-400 focus-within:ring-blue-500 focus-within:ring-2 [&>span:first-child]:focus-within:text-blue-500',
input: 'w-full pl-3 pr-8 py-2 border-none text-base text-gray-700 placeholder-gray-400 formkit-multiple:p-0 data-[placeholder="true"]:text-gray-400 formkit-multiple:data-[placeholder="true"]:text-inherit',
selectIcon: 'flex p-[3px] shrink-0 w-5 mr-2 -ml-[1.5em] h-full pointer-events-none',
option: 'formkit-multiple:p-3 formkit-multiple:text-sm text-gray-700'
},
textarea: {
inner: 'flex max-w-md rounded mb-1 ring-1 ring-gray-400 focus-within:ring-blue-500 [&>label:first-child]:focus-within:text-blue-500',
input: 'block w-full h-32 px-3 py-3 border-none text-base text-gray-700 placeholder-gray-400 focus:shadow-outline',
},
// PRO input styles
autocomplete: {
closeIcon: 'block grow-0 shrink-0 w-3 mr-3.5',
inner: '[&>div>[data-value]]:absolute [&>div>[data-value]]:mb-0',
option: 'grow text-ellipsis',
selection: 'static flex left-0 top-0 right-0 bottom-0 mt-0 mb-2 rounded bg-gray-100',
},
datepicker: {
inner: 'relative',
panelWrapper: 'absolute top-[calc(100%_+_0.5em)] drop-shadow-[0_0_1.25em_rgba(0,0,0,.25)] rounded-md p-5 bg-white z-10',
panelHeader: 'grid grid-cols-[2.5em_1fr_2.5em] justify-center items-center border-b-2 mb-4 pb-4',
input: 'selection:bg-blue-400',
monthsHeader: 'flex items-center justify-center col-start-2 col-end-2',
timeHeader: 'flex items-center justify-center col-start-2 col-end-2',
overlayPlaceholder: 'text-gray-400',
months: 'flex flex-wrap',
month: `
flex items-center justify-center
w-[calc(33%_-_1em)] m-2 p-2 rounded-md
bg-gray-200
aria-selected:bg-blue-500 aria-selected:text-white
focus:outline focus:outline-2 focus:outline-blue-500 focus:outline-offset-2 focus:bg-white focus:text-black
data-[is-extra=true]:opacity-25
formkit-disabled:opacity-50 formkit-disabled:cursor-default formkit-disabled:pointer-events-none
`,
yearsHeader: 'flex items-center justify-center col-start-2 col-end-2',
years: 'flex flex-wrap max-w-[35em]',
year: `
flex items-center justify-center
w-[calc(20%_-_1em)] m-2 p-2 rounded-md
bg-gray-200
aria-selected:bg-blue-500 aria-selected:text-white
focus:outline focus:outline-2 focus:outline-blue-500 focus:outline-offset-2 focus:bg-white focus:text-black
data-[is-extra=true]:opacity-25
formkit-disabled:opacity-50 formkit-disabled:cursor-default formkit-disabled:pointer-events-none
`,
weekDays: 'flex',
weekDay: 'flex w-[2.25em] h-[1em] m-1 items-center justify-center rounded-md font-medium lowercase',
week: 'flex formkit-disabled:opacity-50 formkit-disabled:cursor-default formkit-disabled:pointer-events-none',
dayCell: `
flex items-center justify-center
w-[2.25em] h-[2.25em] m-1 p-2 rounded-md
bg-gray-200
aria-selected:bg-blue-500 aria-selected:text-white
focus:outline focus:outline-2 focus:outline-blue-500 focus:outline-offset-2 focus:bg-white focus:text-black
data-[is-extra=true]:opacity-25
formkit-disabled:opacity-50 formkit-disabled:cursor-default formkit-disabled:pointer-events-none
`,
timeInput: 'w-full border-2 border-gray-300 rounded-md p-2 my-[2em] focus-visible:outline-blue-500',
daysHeader: 'flex items-center justify-center',
prev: 'mr-auto px-3 py-1 hover:bg-gray-100 hover:rounded-lg col-start-1 col-end-1',
prevLabel: 'hidden',
prevIcon: 'flex w-3 select-none [&>svg]:w-full',
dayButton: 'appearance-none cursor-pointer px-3 py-1 border-2 rounded-lg mx-1 hover:border-blue-500',
monthButton: 'appearance-none cursor-pointer px-3 py-1 border-2 rounded-lg mx-1 hover:border-blue-500',
yearButton: 'appearance-none cursor-pointer px-3 py-1 border-2 rounded-lg mx-1 hover:border-blue-500',
next: 'ml-auto px-3 py-1 hover:bg-gray-100 hover:rounded col-start-3 col-end-3',
nextLabel: 'hidden',
nextIcon: 'flex w-3 select-none [&>svg]:w-full',
openButton: `
appearance-none border-0 bg-transparent flex p-0 self-stretch cursor-pointer
focus-visible:outline-none focus-visible:text-white focus-visible:bg-blue-500
`,
calendarIcon: 'flex w-8 grow-0 shrink-0 self-stretch select-none [&>svg]:w-full [&>svg]:m-auto [&>svg]:max-h-[1em] [&>svg]:max-w-[1em]',
},
rating: {
inner: 'relative flex items-center w-[8em] formkit-disabled:bg-transparent',
itemsWrapper: 'w-full',
onItems: 'text-yellow-400',
onItemWrapper: '[&>*]:w-full [&>svg]:h-auto [&>svg]:max-w-none [&>svg]:max-h-none',
offItems: 'text-gray-500',
offItemWrapper: '[&>*]:w-full [&>svg]:h-auto [&>svg]:max-w-none [&>svg]:max-h-none'
},
repeater: {
content: 'grow p-3 flex flex-col align-center',
controlLabel: 'absolute opacity-0 pointer-events-none',
controls: 'flex flex-col items-center justify-center bg-gray-100 p-3',
downControl: 'hover:text-blue-500 disabled:hover:text-inherit disabled:opacity-25',
fieldset: 'py-4 px-5',
help: 'mb-2 mt-1.5',
item: 'flex w-full mb-1 rounded border border-gray-200',
moveDownIcon: 'block w-3 my-1',
moveUpIcon: 'block w-3 my-1',
removeControl: 'hover:text-blue-500 disabled:hover:text-inherit disabled:opacity-25',
removeIcon: 'block w-5 my-1',
upControl: 'hover:text-blue-500 disabled:hover:text-inherit disabled:opacity-25'
},
slider: {
outer: 'max-w-md',
help: 'mt-0 mb-1',
sliderInner: 'flex items-center py-1 [&>.formkit-max-value]:mb-0 [&>.formkit-max-value]:ml-8 [&>.formkit-max-value]:shrink [&>.formkit-max-value]:grow-0 [&>.formkit-icon]:bg-none [&>.formkit-icon]:border-none [&>.formkit-icon]:p-0 [&>.formkit-icon]:w-4 [&>.formkit-prefix-icon]:mr-2 [&>.formkit-suffix-icon]:ml-2 [&[data-has-mark-labels="true"]_.formkit-track]:mb-4',
track: 'grow relative z-[3] py-1',
trackWrapper: 'px-[2px] rounded-full bg-gray-200',
trackInner: 'h-[6px] mx-[2px] relative',
fill: 'h-full rounded-full absolute top-0 mx-[-4px] bg-blue-500',
marks: 'absolute pointer-events-none left-0 right-0 top-0 bottom-0',
mark: 'absolute top-1/2 w-[3px] h-[3px] rounded-full -translate-x-1/2 -translate-y-1/2 bg-gray-400 data-[active="true"]:bg-white',
markLabel: 'absolute top-[calc(100%+0.5em)] left-1/2 text-gray-400 text-[0.66em] -translate-x-1/2',
handles: 'm-0 p-0 list-none',
handle: 'group w-4 h-4 rounded-full bg-white absolute top-1/2 left-0 z-[2] -translate-x-1/2 -translate-y-1/2 shadow-[inset_0_0_0_1px_rgba(0,0,0,0.1),0_1px_2px_0_rgba(0,0,0,0.8)] focus-visible:outline-0 focus-visible:ring-2 ring-blue-500 data-[is-target="true"]:z-[3]',
tooltip: 'absolute bottom-full left-1/2 -translate-x-1/2 -translate-y-[4px] bg-blue-500 text-white py-1 px-2 text-xs leading-none whitespace-nowrap rounded-sm opacity-0 pointer-events-none transition-opacity after:content-[""] after:absolute after:top-full after:left-1/2 after:-translate-x-1/2 after:-translate-y-[1px] after:border-4 after:border-transparent after:border-t-blue-500 group-hover:opacity-100 group-focus-visible:opacity-100 group-data-[show-tooltip="true"]:opacity-100',
linkedValues: 'flex items-start justify-between',
minValue: 'grow max-w-[45%] mb-0 relative [&_.formkit-inner::after]:content-[""] [&_.formkit-inner::after]:absolute [&_.formkit-inner::after]:left-[105%] [&_.formkit-inner::after]:-translate-y-1/2 [&_.formkit-inner::after]:w-[10%] [&_.formkit-inner::after]:h-[1px] [&_.formkit-inner::after]:bg-gray-500',
maxValue: 'grow max-w-[45%] mb-0 relative',
chart: 'relative z-[2] mb-2 flex justify-between items-center w-full aspect-[3/1]',
chartBar: 'absolute bottom-0 h-full bg-gray-400 opacity-[.66] data-[active="false"]:opacity-[.25]',
},
taglist: {
input: 'px-1 py-1 w-[0%] grow',
removeSelection: 'w-2.5 mx-1 self-center text-black leading-none',
tag: 'flex items-center my-1 p-1 bg-gray-200 text-xs rounded-full',
tagWrapper: 'mr-1 focus:outline-none focus:text-white [&>div]:focus:bg-blue-500 [&>div>button]:focus:text-white',
tagLabel: 'pl-2 pr-1',
tags: 'flex items-center flex-wrap w-full py-1.5 px-2',
},
toggle: {
altLabel: 'block w-full mb-1 font-bold text-sm',
inner: '$reset inline-block mr-2',
input: 'peer absolute opacity-0 pointer-events-none',
innerLabel: 'text-[10px] font-bold absolute left-full top-1/2 -translate-x-full -translate-y-1/2 px-1',
thumb: 'relative left-0 aspect-square rounded-full transition-all w-5 bg-gray-100',
track: 'p-0.5 min-w-[3em] relative rounded-full transition-all bg-gray-400 peer-checked:bg-blue-500 peer-checked:[&>div:last-child]:left-full peer-checked:[&>div:last-child]:-translate-x-full peer-checked:[&>div:first-child:not(:last-child)]:left-0 peer-checked:[&>div:first-child:not(:last-child)]:translate-x-0',
valueLabel: 'font-bold text-sm',
wrapper: 'flex flex-wrap items-center mb-1'
},
transferlist: {
outer: `
[&_.dnd-placeholder]:bg-blue-500 [&_.dnd-placeholder]:text-white
[&_.dnd-placeholder_svg]:text-white
[&_.dnd-children-hidden]:w-full [&_.dnd-children-hidden]:p-0 [&_.dnd-children-hidden]:flex [&_.dnd-children-hidden]:flex-col [&_.dnd-children-hidden]:border-none
[&_.dnd-children-hidden_span]:hidden
[&_.dnd-children-hidden_.formkit-transferlist-option]:hidden
[&_.dnd-multiple-selections_span]:inline-block
[&_.dnd-multiple-selections_.formkit-transferlist-option]:inline-block
`,
fieldset: '$reset max-w-2xl',
wrapper: 'flex max-h-[350px] flex-col sm:flex-row justify-between w-full max-w-none',
help: 'pb-2',
transferlist: 'sm:w-3/5 shadow-md flex flex-col min-h-[350px] max-h-[350px] border rounded overflow-hidden select-none bg-gray-50',
transferlistHeader: 'flex bg-gray-100 justify-between items-center border-b p-3',
transferlistHeaderItemCount: 'ml-auto text-sm',
transferlistListItems: 'list-none bg-gray-50 h-full sm:max-w-xs overflow-x-hidden overflow-y-auto',
transferlistListItem: 'pl-8 relative aria-selected:bg-blue-600 aria-selected:data-[is-active=true]:bg-blue-600 aria-selected:text-white aria-selected:data-[is-active=true]:text-white first:-mt-px first:border-t py-2 px-3 flex relative border-b bg-white data-[is-active=true]:text-blue-500 data-[is-active=true]:bg-gray-100 cursor-pointer group-data-[is-max=true]:cursor-not-allowed items-center',
transferlistOption: 'text-sm',
transferControls: 'flex sm:flex-col justify-center mx-auto my-2 sm:mx-2 sm:my-auto border rounded',
transferlistButton: 'text-sm disabled:cursor-not-allowed disabled:bg-gray-200 disabled:opacity-50 first:rounded-l last:rounded-r sm:first:rounded-t sm:last:rounded-b appearance-none p-2 m-0 cursor-pointer h-10 border-none rounded-none bg-gray-50 hover:outline disabled:hover:outline-none hover:outline-1 hover:outline-black hover:text-blue-500 disabled:hover:text-current hover:z-10',
sourceEmptyMessage: 'appearance-none border-none w-full p-0 m-0 text-center text-gray-500 italic',
sourceListItems: 'group-data-[is-max=true]:opacity-50',
targetEmptyMessage: 'appearance-none border-none w-full p-0 m-0 text-center text-gray-500 italic',
emptyMessageInner: 'flex items-center justify-center p-2 text-sm',
transferlistControls: 'bg-white px-3 py-2 border-b',
transferlistSearch: 'flex border rounded items-center',
transferlistSearchInput: 'border-none p-1 w-full bg-transparent outline-none text-sm',
controlLabel: 'hidden',
selectedIcon: 'w-3 absolute left-3 select-none',
fastForwardIcon: 'w-10 flex select-none [&>svg]:m-auto [&>svg]:w-full [&>svg]:max-w-[1rem] [&>svg]:max-h-[1rem] rotate-90 sm:rotate-0',
moveRightIcon: 'w-10 flex select-none [&>svg]:m-auto [&>svg]:w-full [&>svg]:max-w-[1rem] [&>svg]:max-h-[1rem] rotate-90 sm:rotate-0',
moveLeftIcon: 'w-10 flex select-none [&>svg]:m-auto [&>svg]:w-full [&>svg]:max-w-[1rem] [&>svg]:max-h-[1rem] rotate-90 sm:rotate-0',
rewindIcon: 'w-10 flex select-none [&>svg]:m-auto [&>svg]:w-full [&>svg]:max-w-[1rem] [&>svg]:max-h-[1rem] rotate-90 sm:rotate-0',
}
}
这是我们的 Tailwind 主题应用于所有可用的 FormKit 输入时的样子:
FormKit 输入附带了自己的 decorator
图标,可以替代通常附带复选框、单选按钮、选择输入等的浏览器默认样式。
如果您想在 Tailwind 主题中使用这些类型的图标,请确保从 @formkit/icons
中导入它们,并将它们包含在您的 FormKit 配置中。
现在我们已经有了!在整个项目中,所有的 FormKit 输入都使用 Tailwind 实用程序类进行了样式设置。
如果我们需要在项目中覆盖任何特定的一次性操作,我们可以使用 section-key class props 或在项目中给定的 FormKit
输入上的 classes 属性,这在本指南的开头部分已经介绍过了。
在执行覆盖时,特别重要的是类列表的特殊 $reset
修饰符。当 FormKit 类系统遇到一个 $reset
类时,它将擦除给定部分的当前类列表,并且只收集在遇到 $reset
令牌之后出现的类名。在像 Tailwind 这样的系统中,这是非常有价值的,因为在偏离我们的主题时,不必为每个全局配置的类编写覆盖类或单独禁用类:
本指南已经介绍了如何创建一个覆盖 FormKit 中包含的所有输入类型的 Tailwind 主题,但在您自己的项目中还可以做更多的事情。
以下是一些进一步扩展本指南的方法:
dark:
修饰符添加暗黑模式支持。formkit-invalid:formkit-submitted:
,在用户尝试提交不完整表单时为无效字段添加额外强调。希望本指南能帮助您了解如何将类应用到 FormKit 输入以及如何利用 @formkit/themes
包中的 formKitTailwind
插件在您的 FormKit 项目中使用 Tailwind。如果您想深入了解,请阅读关于 FormKit 的核心内部 和 FormKit 模式!