Styles and Layout
Learn about the many ways how Vueform styles and layouts can be customized.
Size
We can use any element in three different sizes: sm, md and lg. The md is the default one, while we can switch to eg. sm using size prop:
<TextElement label="Small" placeholder="Small" size="sm" ... />
<TextElement label="Medium" placeholder="Medium" ... />
<TextElement label="Large" placeholder="Large" size="lg" ... />Views
Some elements and components have alternative views, that we can use with view option:
<!-- Default view -->
<CheckboxgroupElement :items="['Vue.js', 'React', 'AngularJS']" ... />
<!-- Tabs view -->
<CheckboxgroupElement view="tabs" :items="['Vue.js', 'React', 'AngularJS']" ... />
<!-- Blocks view -->
<CheckboxgroupElement view="blocks" :items="[
{ value: 'Vue.js', label: 'Vue.js', description: 'Vue.js framework' },
{ value: 'React', label: 'React', description: 'React framework' },
{ value: 'AngularJS', label: 'AngularJS', description: 'AngularJS framework' },
]" ... />Views for Children
The view we define for an element will be applied to each of its child components (if the view exist for them with the same name). If we want to change the views of the element's child components, we can use views property, which overrides the elements default view option:
<TextElement :views="{
ElementLabel: 'highlighted'
}" ... />In this case ElementLabel_highlighted template will be used instead of ElementLabel for this specific element if it registered as a template.
If a component has multiple available views, they can be found at the Components reference, under the components Views section.
Columns
We can pass an object to :columns prop of any element to define its column widths:
<TextElement label="Label" :columns="{ container: 12, label: 3, wrapper: 12 }" ... />Columns add grid classes (like col-6 or w-1/2) to container, label and wrapper of an element, where:
containeris the outermost DOM of the element that contains bothlabelandwrapperlabelcontains the labelwrappercontains the custom parts of element.
Here they are more visually:
container: 12label: 3wrapper: 12The value of container defines the size of the element's container. 12 will result in full width, 6 in half, 4 in third and so on.
The value of label defines the amount of space the label should take up within the container. If the container is 12 and label is 6 the label is going to take up half the space and the element's wrapper will the other half (which is calculated automatically). If the container is 6 and label is 6, the label will only take up one forth and the element's wrapper the rest. In case the label has full width (12) the element's wrapper will also take up full space instead of being zero.
The value of wrapper defines the size of the element's wrapper within the space left for it in the container after subtracting the size of the label. If the container is 12 and label is 4 the space left for the element's wrapper is 8. In this case if the wrapper value is 12 it will take up the full space left for the it (which is 8) while if it is changed to 6 it will only take up half the space left for it (4):
<TextElement label="Label" :columns="{ container: 12, label: 4, wrapper: 12 }" />
<TextElement label="Label" :columns="{ container: 12, label: 4, wrapper: 6 }" />Note that while the size of the element's wrapper container changes the size of other extras like a description or error won't be limited to the field's space. Instead it will take up the full space left by the label:
<TextElement
label="Label"
description="Lorem ipsum dolor sit amet, consectetur adipiscing elit"
:columns="{ container: 12, label: 4, wrapper: 6 }"
...
/>We can set the value of columns as a number which case the container will receive its value without affecting the default label and wrapper values:
<TextElement label="Label" :columns="6" ... /> <!-- { container: 6, label: 3, wrapper: 12 } -->Responsive Columns
We can also define column values for different breakpoints using the theme system's breakpoints like sm, md, etc. as keys:
<TextElement label="Label" :columns="{
default: { container: 12, label: 12, wrapper: 12 },
sm: { container: 12, label: 4, wrapper: 12 },
md: 12,
lg: { container: 12, label: 2, wrapper: 12 }
}" ... />Vueform is using a mobile-first breakpoint system to accomodate most popular CSS frameworks like Bootstrap or Tailwind CSS. The default breakpoint is the one that will be used below the lowest breakpoint.
The default breakpoints are (based on Tailwind CSS breakpoint system):
| Breakpoint prefix | Minimum width | CSS |
|---|---|---|
sm | 640px | @media (min-width: 640px) { ... } |
md | 768px | @media (min-width: 768px) { ... } |
lg | 1024px | @media (min-width: 1024px) { ... } |
xl | 1280px | @media (min-width: 1280px) { ... } |
2xl | 1536px | @media (min-width: 1536px) { ... } |
Customizing Breakpoints
We can customize the form's breakpoints to fit our project's breakpoints.
In Tailwind Theme
When using tailwind theme we don't have to do anything specific beside customizing the tailwind.config.js to use certain breakpoints. Once we done that the same breakpoints can used for column definition.
Eg. customizing the tailwind.config.js with the following values:
// tailwind.config.js
module.exports = {
theme: {
screens: {
'tablet': '640px',
// => @media (min-width: 640px) { ... }
'laptop': '1024px',
// => @media (min-width: 1024px) { ... }
'desktop': '1280px',
// => @media (min-width: 1280px) { ... }
},
}
}Allows us to use the following breakpoints for columns for any Vueform element:
<TextElement label="Label" :columns="{
default: { container: 12, label: 12, wrapper: 12 },
tablet: { container: 12, label: 4, wrapper: 12 },
laptop: { container: 12, label: 3, wrapper: 12 },
desktop: { container: 12, label: 2, wrapper: 12 },
}" ... />In Other Themes
In other themes like the default vueform theme we need to customize the breakpoints via a scss variable.
The first step is to replace our original css import with the scss counterpart:
// eg. index.css
// Original import
@import './../node_modules/@vueform/vueform/dist/vueform.css';Instead of using a css create an scss file and import that somewhere in your project. The scss file should contain the following:
// eg. index.scss
// New import
@import './../node_modules/@vueform/vueform/themes/vueform/scss/index.scss';Now we can rely on scss variables and we can override the breakpoints:
// eg. index.scss
$grid-breakpoints: (
tablet: 640px,
laptop: 1024px,
desktop: 1280px,
);
// New import
@import './../node_modules/@vueform/vueform/themes/vueform/scss/index.scss';This will allow us to use our custom breakpoints in any Vueform element:
<TextElement label="Label" :columns="{
default: { container: 12, label: 12, wrapper: 12 },
tablet: { container: 12, label: 4, wrapper: 12 },
laptop: { container: 12, label: 3, wrapper: 12 },
desktop: { container: 12, label: 2, wrapper: 12 },
}" ... />Changing Classes
Basics
Each component has a template defined by the theme. The template includes 3 parts:
- the actual
<template>of the component - the classes that can be used in the template in
defaultClassesobject indata() <style>if the component is using named classes and not utility classes.
Themes also include a
classes.jsfile that overrides anydefaultClassesdefined by the templates, which is useful if we want to centralize class definitions. Also, the<template>part is provided by a theme calledblankfor each theme, so they don't have to be reimplemented in different themes only when something requires structural change.
Let's take a look at the <template> part of TextElement component:
<!-- From: @vueform/vueform/themes/blank/templates/elements/TextElement.vue -->
<template>
<ElementLayout class="classes.container">
<template element>
<div class="classes.inputContainer">
<ElementAddon type="before" ... />
<ElementAddon type="after" ... />
<ElementLabelFloating ... />
<ElementLoader ... />
<input class="classes.input" ... />
</div>
</template>
</ElementLayout>
</template>The template has been simplified for better readability, but what's important for us are the classes.
We can see that TextElement uses three class names: container, inputContainer and input.
These classes are defined by themes differently, here's how they look like in vueform theme:
// From: @vueform/vueform/themes/vueform/templates/elements/TextElement.vue
{
container: '',
inputContainer: 'vf-input-group',
// ...
input: 'vf-input',
input_enabled: '',
input_disabled: '',
input_sm: 'vf-input-sm',
input_md: '',
input_lg: 'vf-input-lg',
// ...
$input: (classes, { isDisabled, Size }) => ([
classes.input,
classes[`input_${Size}`],
isDisabled ? classes.input_disabled : classes.input_enabled
])
}And this is how tailwind theme defines them:
// From: @vueform/vueform/themes/tailwind/classes.js
{
container: '',
inputContainer: 'w-full flex',
// ...
input: 'w-full form-border form-border-color z-1 transition-shadow addon-before:form-rounded-l-none outline-none addon-after:form-rounded-r-none',
input_enabled: 'focus:form-ring',
input_disabled: 'form-bg-disabled form-text-disabled',
input_sm: 'form-p-input-sm form-rounded-sm form-text-sm',
input_md: 'form-p-input form-rounded',
input_lg: 'form-p-input-lg form-rounded-lg form-text-lg with-floating:form-p-input-floating-lg',
// ...
$input: (classes, { isDisabled, Size }) => ([
classes.input,
classes[`input_${Size}`],
isDisabled ? classes.input_disabled : classes.input_enabled
]),
}(The TextElement template changed in 1.2.0, but it's easier to grasp the concept with this example.)
We can see that input also has conditional classes:
input_enabled- added when the input is enabledinput_disabled- added when the input is disabledinput_sm- added when the input's size issminput_md- added when the input's size ismdinput_lg- added when the input's size islg.
These classes are only added to input if their conditions are fulfilled.
If a class name is also defined as a
functionand starts with a$it can return a dynamic value for the original class name.
Based on this, the actual <input> field have different classes in different states.
For example in tailwind theme the TextElement's input class has input_md and input_enabled added by default:
input: `w-full form-border form-border-color z-1 transition-shadow
addon-before:form-rounded-l-none outline-none
addon-after:form-rounded-r-none ` // from `input`
+ `form-p-input form-rounded ` // from `input_md`
+ `focus:form-ring` // from `input_enabled`If the element becomes disabled and uses sm size it'll change to this:
input: `w-full form-border form-border-color z-1 transition-shadow
addon-before:form-rounded-l-none outline-none
addon-after:form-rounded-r-none ` // from `input`
+ `form-p-input-sm form-rounded-sm form-text-sm ` // from `input_sm`
+ `form-bg-disabled form-text-disabled` // from `input_disabled`This is how Vueform manages the different states and appearances of components driven by external parameters like Size or isDisabled.
Class Helpers
This is how TextElement gets rendered in the browser using tailwind theme:
If we check the source code we'll see it's complete HTML structure:
<div class="form-col w-full">
<div class="form-row flex flex-wrap form-mb-gutter">
<label for="text" class="form-col pr-4 form-py-input-border w-3/12">
<span>Text</span>
</label>
<div class="flex-1 w-9/12">
<div class="form-col w-full"></div>
<div class="form-col w-full">
<div class="w-full flex">
<input type="text" name="text" id="text" class="w-full form-border form-border-color z-1 transition-shadow addon-before:form-rounded-l-none outline-none addon-after:form-rounded-r-none form-p-input form-rounded focus:form-ring">
</div>
</div>
<div class="form-col w-full"></div>
</div>
</div>
</div>(The TextElement template changed in 1.2.0, but it's easier to grasp the concept with this example.)
We have a bunch of utility classes assigned to different DOM elements, but we have no idea which component or class name they belong to and makes it hard to customize them.
To make development easier we've introduced class helpers in 1.2.0 which prepends descriptive names to the class lists.
Let's enable classHelpers in vueform.config.js:
// vueform.config.js
import { defineConfig } from '@vueform/vueform'
export default defineConfig({
classHelpers: true,
// ...
})And see how our source changes:
<div class="ElementLayout.container--> form-col ElementLayout.container_md--> w-full TextElement.container-->">
<div class="ElementLayout.outerWrapper--> form-row flex flex-wrap ElementLayout.outerWrapper_single--> ElementLayout.outerWrapper_single_md--> form-mb-gutter">
<label for="text" class="ElementLabel.container--> form-col pr-4 ElementLabel.container_md--> form-py-input-border w-3/12">
<span>Text</span>
</label>
<div class="ElementLayout.innerContainer--> flex-1 w-9/12">
<div class="ElementLayout.innerWrapperBefore--> form-col w-full"></div>
<div class="ElementLayout.innerWrapper--> form-col w-full">
<div class="TextElement.inputContainer--> w-full flex">
<input type="text" name="text" id="text" class="TextElement.input--> w-full form-border form-border-color z-1 transition-shadow addon-before:form-rounded-l-none outline-none addon-after:form-rounded-r-none TextElement.input_md--> form-p-input form-rounded TextElement.input_enabled--> focus:form-ring">
</div>
</div>
<div class="ElementLayout.innerWrapperAfter--> form-col w-full"></div>
</div>
</div>
</div>Helper classes were prepended to our class lists which contain information about where the following classes are coming from.
The first part of a highlighted class name is the name of the component, the second is the name of the class.
With this knowledge we can identify which class comes from which component's class name and now we're ready to customize them.
Add Classes
To add classes to the element we can use addClass option.
Let's say we want bold letters in our input field, so add font-bold utility class to input:
<TextElement name="text" label="Text" default="value" :add-class="{
input: 'font-bold'
}" />Result:
The font-bold class got appended to the class to TextElement.input section and now it is rendered among classes:
<input type="text" name="text" id="text" class="TextElement.input--> w-full form-border
form-border-color z-1 transition-shadow addon-before:form-rounded-l-none outline-none
addon-after:form-rounded-r-none font-bold TextElement.input_md--> form-p-input
form-rounded TextElement.input_enabled--> focus:form-ring">When adding classes we need to define them as string or array. If we want to use Vue style classes, object (conditional) values must be wrapped in an array.
If we pass a string or an array directly to addClass instead of an object it will be appended to the element's outermost DOM.
Remove Classes
If we want to remove a class from a class list we can use removeClass option.
Let's remove the border related props from the TextElement's input class:
<TextElement name="text" label="Text" default="value" :remove-class="{
inputContainer: ['form-border-width-input', 'form-border-color-input']
}" />Result:
The
form-border-width-inputandform-border-color-inpututility classes are defined by Vueform's Tailwind CSS plugin and they are responsible form setting border width and color based on Tailwind.
When removing classes we need to define the list of classes to be removed in an array.
If we pass an array directly to removeClass instead of an object it will be applied to the element's outermost DOM.
Replace Classes
We can also replace certain classes in a class list instead of removing and adding ones with replaceClass property.
Let's change the original form input border with a thicker black one:
<TextElement name="text" label="Text" default="value" :replace-class="{
inputContainer: {
'form-border-width-input': 'border-2',
'form-border-color-input': ['border-black', 'border-opacity-80']
}
}" />Result:
When replacing classes we must use an object, where the keys are the original class names and the values are the replacements. The keys can only be single classes, while values can contain multiple ones in string or an array. If we want to use Vue style classes, object (conditional) values must be wrapped in an array.
If we pass an object in an array directly to removeClass instead of an object it will be applied to the element's outermost DOM.
Override Classes
If we want to replace an element's class completely we can use overrideClass options.
Let's get rid of the built-in form input looks and replace it with a simple underlined input field:
<TextElement name="text" label="Text" default="value" :override-class="{
inputContainer: 'border-b-2 bg-transparent w-full transition-all',
inputContainer_default: 'border-black',
inputContainer_focused: 'border-red-500',
inputContainer_md: 'h-10',
}" />Result:
We changed multiple classes in order to get rid of border radius and focus ring as well.
When overriding classes we can use string or array values. If we want to use Vue style classes, object (conditional) values must be wrapped in an array.
If we pass an string or an array directly to overrideClass instead of an object it will override the element's outermost DOM's class.
Modify Multiple Components
We can modify multiple components used by the element at the same time. For example we can add font-bold to TextElement's input and ElementLabel's container field as well:
<TextElement name="text" label="Text" default="value" :add-classes="{
TextElement: {
input: 'font-bold'
},
ElementLabel: {
container: 'font-bold'
}
}" />Result:
We can modify multiple classes the same way with removeClasses, replaceClasses and overrideClasses as well.
Modify FileElement in MultifileElement
One thing to note is that these class modifiers will only be applied to element and its child components, but not to its child elements. So if we want to change classes for eg. FileElement in MultifileElement (which is like a ListElement with multiple FileElements), we have to pass the class modifiers directly to FileElement via file prop:
<MultifileElement name="multifile" label="Multifile" :file="{
overrideClasses: {
FilePreview: {
upload: 'hidden'
},
}
}" />Conditional Classes
Conditional classes can be used in all modification types by passing a function as the options value which should return an object. The first param of the function is the form$ instance:
<TextElement :add-classes="(form$, el$, comp$) => ({
TextElement: {
container: {
'text-red-500': form$.el$('age')?.value < 18
}
}
})" ... />The
el$(second param) can only be used for components that hasel$(component instance) property available, eg.TextElementorElementLabelbut not eg.VueformorFormElements. If you are referencing custom components make sure they haveel$available by injectingel$in any component used by an element type component.
The comp$ (third param) is a reference to the component itself. It always takes up the value of the current component when going through child components. Eg. if used for ElementLayout the comp$ will be the instance of ElementLayout and CheckboxgroupCheckbox when used for that one. Therefore it's important to use optional chaining (comp$?.something) in each case.
This can be used for element, form and config level class modifiers.
Replacing Templates
Sometimes, it's not enough to change the classes of the component's existing DOM structure but we need to alter the DOM structure itself. This is when we can replace the template of an existing component.
In this example we are going to replace the template of the ElementError component and we will add a bold ERROR: prefix to all error messages and make all errors align to right.
Here's how ElementError looks like by default:
And here's what we will achieve:
DOM Structure
In order to replace an existing component's template it's advisable to use its exising counterpart's template as a starting point.
We can do this by checking out /themes/blank/templates directory in the @vueform/vueform repo, which is the basis for every theme's DOM structure.
Here's how ElementError's template looks like (source):
<!-- From: @vueform/vueform/themes/blank/templates/ElementError.vue -->
<template>
<div
v-if="error"
:class="classes.container"
:id="id"
aria-live="assertive"
v-html="error"
></div>
</template>
<script>
export default {
name: 'ElementError',
data() {
return {
merge: true,
defaultClasses: {
container: '',
}
}
}
}
</script>
<style lang="scss">
</style>We know that we want to prefix all element messages with a bold ERROR: string so we want to achieve something like this on the DOM level:
<template>
<div
v-if="error"
:class="classes.container"
:id="id"
aria-live="assertive"
>
<b>ERROR: </b><span v-html="error" />
</div>
</template>So in order to achieve let's create a copy of the existing template as CustomElementError.vue and apply the changes:
<!-- CustomElementError.vue -->
<template>
<div
v-if="error"
:class="classes.container"
:id="id"
aria-live="assertive"
>
<b>ERROR: </b><span v-html="error" />
</div>
</template>
<script>
export default {
name: 'ElementError',
data() {
return {
merge: true,
defaultClasses: {
container: '',
}
}
}
}
</script>
<style lang="scss">
</style>Now we have our custom DOM structure, but we haven't dealt with styles yet.
Applying Styles
We can see that our component only uses a single class called container:
<!-- CustomElementError.vue -->
<template>
<div
v-if="error"
:class="classes.container"
:id="id"
aria-live="assertive"
>
<b>ERROR: </b><span v-html="error" />
</div>
</template>The
classesobject is a computed variable that merges the component's default classes with everything that might manipulate them externally, like the theme's class definition file, global overrides, on the flight overrides, etc. That's all we need to know for now.
Utility Based Themes (eg. tailwind)
If we are using a utility based theme like tailwind classes will be automatically filled in from the theme's class definition file as we are using merge: true in data export.
So this is what basically gets applied to ElementError component:
// @vueform/vueform/themes/tailwind/classes.js
export default {
// ...
ElementError: {
container: 'form-color-danger block',
container_sm: 'form-text-small-sm mt-0.5',
container_md: 'form-text-small mt-1',
container_lg: 'form-text-small-lg mt-1',
$container: (classes, { Size }) => ([
classes.container,
classes[`container_${Size}`],
]),
},
// ...
}If we want to customize it in our custom component that we just created, we can copy them to our component and customize them while setting merge: false:
<!-- CustomElementError.vue -->
<template>
<div
v-if="error"
:class="classes.container"
:id="id"
aria-live="assertive"
>
<b>ERROR: </b><span v-html="error" />
</div>
</template>
<script>
export default {
name: 'ElementError',
data() {
return {
merge: false,
defaultClasses: {
container: 'form-color-danger block text-right', // <-- we added `text-right`
container_sm: 'form-text-small-sm mt-0.5',
container_md: 'form-text-small mt-1',
container_lg: 'form-text-small-lg mt-1',
$container: (classes, { Size }) => ([
classes.container,
classes[`container_${Size}`],
]),
}
}
}
}
</script>
<style lang="scss">
</style>The
merge: trueoption means that the component's classes should be merge with the ones defined in the theme's class definition file (if any) where the ones in the class definition get merged to the ones defined in the component'sdefaultClassesdata prop.
If it's set tomerge: falsethe classes defined in the component will be used.
Class Name Based Themes (eg. vueform)
If we are using a class name based theme like vueform we need to copy the original class names to our component from ElementError's defaultClasses and set merge: false:
<!-- CustomElementError.vue -->
<template>
<div
v-if="error"
:class="classes.container"
:id="id"
aria-live="assertive"
>
<b>ERROR: </b><span v-html="error" />
</div>
</template>
<script>
export default {
name: 'ElementError',
data() {
return {
merge: false,
defaultClasses: {
container: 'vf-element-error',
container_sm: 'vf-element-error-sm',
container_md: '',
container_lg: 'vf-element-error-lg',
$container: (classes, { Size }) => ([
classes.container,
classes[`container_${Size}`],
]),
}
}
}
}
</script>
<style lang="scss">
</style>We are setting
merge: falsejust to keep consistency, becausemerge: truewould mean that the classes defined in the component'sdefaultClassesshould be merge with the theme's class definition object, but class name based themes likevueformdoes not have any.
Great, now we have the default styles applied to our ElementError component, so let's add a new class that positions the error message to the right.
<!-- CustomElementError.vue -->
<template>
<div
v-if="error"
:class="classes.container"
:id="id"
aria-live="assertive"
>
<b>ERROR: </b><span v-html="error" />
</div>
</template>
<script>
export default {
name: 'ElementError',
data() {
return {
merge: false,
defaultClasses: {
container: 'vf-element-error vf-element-error-right',
container_sm: 'vf-element-error-sm',
container_md: '',
container_lg: 'vf-element-error-lg',
$container: (classes, { Size }) => ([
classes.container,
classes[`container_${Size}`],
]),
}
}
}
}
</script>
<style lang="scss">
.vf-element-error-right {
text-align: right;
}
</style>Sidenote: Using Computed Classes ($classname)
You probably noticed that even though ElementError only has a single container class it has couple of alterations of that and you might be wondering what they are.
Vueform resolve classes dynamically and when it notices that a class name is also defined with a $ prefix (like $container) it treats it as a sort of computed value. What it means is that instead of returning the plain value of container it returns the computed value returned by $container.
The a computed class method's first argument is the list of classes the component has, which in our case are:
containercontainer_smcontainer_mdcontainer_lg
The second is the component instance itself, which allows accessing any of properties and methods.
Let's see what $container does in our example:
$container: (classes, { Size }) => ([
classes.container, // returns 'vf-element-error vf-element-error-right'
classes[`container_${Size}`], // returns 'vf-element-error-sm' or '' or 'vf-element-error-lg'
]),- It takes
classesas the first argument, and deconstructsSizefrom the second - It returns the original
containerclass - And it returns the version of container that is suffixed with the size, depending on the
Sizeof element.
Here's how container class is resolved with different sizes:
<TextElement size="sm" />
<!-- vf-element-error vf-element-error-right vf-element-error-sm -->
<TextElement size="md" />
<!-- vf-element-error vf-element-error-right -->
<TextElement size="lg" />
<!-- vf-element-error vf-element-error-right vf-element-error-lg -->To see what properties a component has, check it out in the Components reference.
Registering the Template Globally
The last step is to register out component globally, overriding the original ElementError's template:
// vueform.config.js
import { defineConfig } from '@vueform/vueform'
import CustomElementError from './path/to/CustomElementError.vue'
export default defineConfig({
templates: {
ElementError: CustomElementError,
},
})Now when ElementError gets rendered in any component it will use our custom template and styles:
Single Use
If we don't want to globally override the ElementError component and just use it for a single form or element, we can do it via templates property of <Vueform> component or any element like <TextElement> component:
<template>
<div id="app">
<!-- Will be applied to all elements -->
<Vueform :templates="{ ElementError }">
<TextElement name="text" />
<TextElement name="text2" />
</Vueform>
<Vueform>
<!-- Will be applied to a single elements -->
<TextElement name="text" :templates="{ ElementError }" />
</Vueform>
</div>
</template>
<script setup>
import { ref } from 'vue'
import ElementError from './path/to/CustomElementError.vue'
</script><template>
<div id="app">
<!-- Will be applied to all elements -->
<Vueform :templates="{ ElementError }">
<TextElement name="text" />
<TextElement name="text2" />
</Vueform>
<Vueform>
<!-- Will be applied to a single elements -->
<TextElement name="text" :templates="{ ElementError }" />
</Vueform>
</div>
</template>
<script>
import { ref, markRaw } from 'vue'
import ElementError from './path/to/CustomElementError.vue'
export default {
data: () => ({
// Make sure to use `markRaw` when passing over components
// as data to remove unnecessary reactivity.
ElementError: markRaw(ElementError)
})
}
</script>Alternative Views
In the previous section, Replacing Templates, we learned how we can create a custom version of a Vueform component's template. Here, we will learn how we can use it as an alternative view, instead of overriding the component's original template.
Sticking to our example from the previous section (make sure to read it first), instead of registering the template as a replacement of ElementError let's add an _errorRight suffix to it:
// vueform.config.js
import { defineConfig } from '@vueform/vueform'
import CustomElementError from './path/to/CustomElementError.vue'
export default defineConfig({
// ...
templates: {
ElementError_errorRight: CustomElementError,
}
})Then, we can use this template as an alternative view anytime by applying view="errorRight" on any parent component that uses ElementError:
<!-- All elements in the form will use the "ElementError_errorRight" template -->
<Vueform view="errorRight">
<TextElement ... />
<TextElement ... />
</Vueform>
<Vueform>
<!-- Only this element will use the "ElementError_errorRight" template -->
<TextElement view="errorRight" ... />
<!-- This will use the original `ElementError` -->
<TextElement ... />
</Vueform>We can create alternative views for any component the same way. Just remember when view is defined for an element (or even <Vueform>) it will be passed down to children so all of them will try to use the same alternative template for all of their components if available.
Presets
If we want to apply a set of rules (eg. addClasses, templates, size, etc.) to certain elements or forms we can use presets.
Presets can be created at vueform.config.js under presets property:
// vueform.config.js
import { defineConfig } from '@vueform/vueform'
export default defineConfig({
presets: {
presetName: {
// preset options
}
}
})We can add the same options to a preset that we'd normally add to an element, including:
size- defines the default sizeviews- an object containing key value pairs for component names and their default viewscolumns- a columns objectaddClasses- classes to addremoveClasses- classes to removereplaceClasses- classes to replaceoverrideClasses- classes to overridetemplates- templates to replace.
For example this would add an extra margin to each ElementLabel component where the preset is applied:
// vueform.config.js
import { defineConfig } from '@vueform/vueform'
export default defineConfig({
presets: {
italicLabel: {
addClasses: {
ElementLabel: {
container: 'italic'
}
}
}
}
})Later we can use the presets for an element using presets option:
<template>
<Vueform>
<TextElement label="Normal" ... />
<TextElement label="Italic" :presets="['italicLabel']" ... />
<Vueform>
</template>... or even for forms:
<template>
<Vueform :presets="['italicLabel']">
<TextElement label="Italic" ... />
<TextElement label="Italic" ... />
<Vueform>
</template>The presets will be applied in consecutive order before the element/form options. This means that if a preset eg. adds a class a class it will be added before classes added with element/form's addClasses option.
Form Customization
Just like for presets, the following options can be also applied on a form level:
sizeviewscolumnsaddClassaddClassesremoveClassremoveClassesreplaceClassreplaceClassesoverrideClassoverrideClassespresets
Options defined on form level will take effect before element presets and options. Eg:
<template>
<Vueform size="sm">
<TextElement ... />
<TextElement size="md" ... />
</Vueform>
</template>Will result in:
Classes modified on a higher level can also be modified on a lower level:
<template>
<Vueform :add-classes="{
TextElement: {
input: 'font-bold'
}
}">
<TextElement ... />
<TextElement :replace-class="{
input: {
'font-bold': 'italic'
}
}" ... />
</Vueform>
</template>Will result in:
Global Customization
We can also define the the options mentioned in this chapter globally in vueform.config.js. In config we can use usePresets option to apply presets globally, which will take place before any other setting, global or local.
CSS Vars
Vueform themes use CSS vars under the hood that makes theme customizations a breeze. The different themes (eg. default, Bootstrap, Material) are in fact identicaly structurally and are using the exact same class names, their only difference is how their CSS vars are configured.
To see the available theme check out Themes page.
Global Customization
If we'd like to override some of the CSS vars globally we can define the following in global scope:
:root, :before, :after, * {
--vf-border-color-input: #14b8a6;
--vf-bg-input: #ccfbf1;
}Then all of our forms will be displayed using the global CSS vars setting:
<template>
<Vueform>
<TextElement ... /> <!-- Will be teal -->
<TextElement ... /> <!-- Will be teal -->
</Vueform>
</template>Local Customization
We can also override CSS vars only for certain form instance or even element. To do this we need to add a class name to the form or element and apply the CSS vars for those scopes only:
.form-teal, .element-teal {
--vf-border-color-input: #14b8a6;
--vf-bg-input: #ccfbf1;
}Now if we apply those classes to forms or elements only those will be altered:
<template>
<!-- Teal form -->
<Vueform add-class="form-teal">
<TextElement ... /> <!-- Will be teal -->
<TextElement ... /> <!-- Will be teal -->
</Vueform>
<!-- Teal element -->
<Vueform>
<TextElement ... /> <!-- Will be default -->
<TextElement add-class="element-teal" ... /> <!-- Will be teal -->
</Vueform>
</template>Available CSS Vars
The following CSS vars are the default values for vueform theme and they can be overriden globally or locally.
General Colors
Colors used globally in Vueform components.
// Colors
:root, :before, :after, * {
--vf-primary: #07bf9b;
--vf-primary-darker: #06ac8b;
--vf-danger: #ef4444;
--vf-danger-lighter: #fee2e2;
--vf-success: #10b981;
--vf-success-lighter: #d1fae5;
--vf-gray-50: #f9fafb;
--vf-gray-100: #f3f4f6;
--vf-gray-200: #e5e7eb;
--vf-gray-300: #d1d5db;
--vf-gray-400: #9ca3af;
--vf-gray-500: #6b7280;
--vf-gray-600: #4b5563;
--vf-gray-700: #374151;
--vf-gray-800: #1f2937;
--vf-gray-900: #111827;
--vf-dark-50: #f9fafb;
--vf-dark-100: #f3f4f6;
--vf-dark-200: #e5e7eb;
--vf-dark-300: #d1d5db;
--vf-dark-400: #9ca3af;
--vf-dark-500: #6b7280;
--vf-dark-600: #4b5563;
--vf-dark-700: #374151;
--vf-dark-800: #1f2937;
--vf-dark-900: #111827;
}Ring Settings
Rings appear as an outline when input elements are focused.
// Ring Settings
:root, :before, :after, * {
--vf-ring-width: 2px;
--vf-ring-color: #07bf9b66;
}Link Settings
Links can be displayed with tag: 'a' option with StaticElements.
// Link Settings
:root, :before, :after, * {
--vf-link-color: var(--vf-primary);
--vf-link-decoration: inherit;
}Text Sizing
Texts sizes to use through components.
// Text Sizing
:root, :before, :after, * {
--vf-font-size: 1rem;
--vf-font-size-sm: 0.875rem;
--vf-font-size-lg: 1rem;
--vf-font-size-small: 0.875rem;
--vf-font-size-small-sm: 0.8125rem;
--vf-font-size-small-lg: 0.875rem;
--vf-font-size-h1: 2.125rem;
--vf-font-size-h1-sm: 2.125rem;
--vf-font-size-h1-lg: 2.125rem;
--vf-font-size-h2: 1.875rem;
--vf-font-size-h2-sm: 1.875rem;
--vf-font-size-h2-lg: 1.875rem;
--vf-font-size-h3: 1.5rem;
--vf-font-size-h3-sm: 1.5rem;
--vf-font-size-h3-lg: 1.5rem;
--vf-font-size-h4: 1.25rem;
--vf-font-size-h4-sm: 1.25rem;
--vf-font-size-h4-lg: 1.25rem;
--vf-font-size-h1-mobile: 1.5rem;
--vf-font-size-h1-mobile-sm: 1.5rem;
--vf-font-size-h1-mobile-lg: 1.5rem;
--vf-font-size-h2-mobile: 1.25rem;
--vf-font-size-h2-mobile-sm: 1.25rem;
--vf-font-size-h2-mobile-lg: 1.25rem;
--vf-font-size-h3-mobile: 1.125rem;
--vf-font-size-h3-mobile-sm: 1.125rem;
--vf-font-size-h3-mobile-lg: 1.125rem;
--vf-font-size-h4-mobile: 1rem;
--vf-font-size-h4-mobile-sm: 1rem;
--vf-font-size-h4-mobile-lg: 1rem;
--vf-font-size-blockquote: 1rem;
--vf-font-size-blockquote-sm: 0.875rem;
--vf-font-size-blockquote-lg: 1rem;
--vf-line-height: 1.5rem;
--vf-line-height-sm: 1.25rem;
--vf-line-height-lg: 1.5rem;
--vf-line-height-small: 1.25rem;
--vf-line-height-small-sm: 1.125rem;
--vf-line-height-small-lg: 1.25rem;
--vf-line-height-headings: 1.2;
--vf-line-height-headings-sm: 1.2;
--vf-line-height-headings-lg: 1.2;
--vf-line-height-blockquote: 1.5rem;
--vf-line-height-blockquote-sm: 1.25rem;
--vf-line-height-blockquote-lg: 1.5rem;
--vf-letter-spacing: 0;
--vf-letter-spacing-sm: 0;
--vf-letter-spacing-lg: 0;
--vf-letter-spacing-small: 0;
--vf-letter-spacing-small-sm: 0;
--vf-letter-spacing-small-lg: 0;
--vf-letter-spacing-headings: 0;
--vf-letter-spacing-headings-sm: 0;
--vf-letter-spacing-headings-lg: 0;
--vf-letter-spacing-blockquote: 0;
--vf-letter-spacing-blockquote-sm: 0;
--vf-letter-spacing-blockquote-lg: 0;
}Gutter
The space between elements vertically and horizontally.
// Gutter
:root, :before, :after, * {
--vf-gutter: 1rem;
--vf-gutter-sm: 0.5rem;
--vf-gutter-lg: 1rem;
}Input Spacing
Spacing within input fields.
// Input Spacing
:root, :before, :after, * {
--vf-min-height-input: 2.375rem;
--vf-min-height-input-sm: 2.125rem;
--vf-min-height-input-lg: 2.875rem;
--vf-py-input: 0.375rem;
--vf-py-input-sm: 0.375rem;
--vf-py-input-lg: 0.625rem;
--vf-px-input: 0.75rem;
--vf-px-input-sm: 0.5rem;
--vf-px-input-lg: 0.875rem;
--vf-py-btn: 0.375rem;
--vf-py-btn-sm: 0.375rem;
--vf-py-btn-lg: 0.625rem;
--vf-px-btn: 0.875rem;
--vf-px-btn-sm: 0.75rem;
--vf-px-btn-lg: 1.25rem;
--vf-py-btn-small: 0.25rem;
--vf-py-btn-small-sm: 0.25rem;
--vf-py-btn-small-lg: 0.375rem;
--vf-px-btn-small: 0.625rem;
--vf-px-btn-small-sm: 0.625rem;
--vf-px-btn-small-lg: 0.75rem;
--vf-py-group-tabs: var(--vf-py-input);
--vf-py-group-tabs-sm: var(--vf-py-input-sm);
--vf-py-group-tabs-lg: var(--vf-py-input-lg);
--vf-px-group-tabs: var(--vf-px-input);
--vf-px-group-tabs-sm: var(--vf-px-input-sm);
--vf-px-group-tabs-lg: var(--vf-px-input-lg);
--vf-py-group-blocks: 0.75rem;
--vf-py-group-blocks-sm: 0.625rem;
--vf-py-group-blocks-lg: 0.875rem;
--vf-px-group-blocks: 1rem;
--vf-px-group-blocks-sm: 1rem;
--vf-px-group-blocks-lg: 1rem;
--vf-py-tag: 0;
--vf-py-tag-sm: var(--vf-py-tag);
--vf-py-tag-lg: var(--vf-py-tag);
--vf-px-tag: 0.4375rem;
--vf-px-tag-sm: var(--vf-px-tag);
--vf-px-tag-lg: var(--vf-px-tag);
--vf-py-slider-tooltip: 0.125rem;
--vf-py-slider-tooltip-sm: 0.0625rem;
--vf-py-slider-tooltip-lg: 0.1875rem;
--vf-px-slider-tooltip: 0.375rem;
--vf-px-slider-tooltip-sm: 0.3125rem;
--vf-px-slider-tooltip-lg: 0.5rem;
--vf-py-blockquote: 0.25rem;
--vf-py-blockquote-sm: 0.25rem;
--vf-py-blockquote-lg: 0.25rem;
--vf-px-blockquote: 0.75rem;
--vf-px-blockquote-sm: 0.75rem;
--vf-px-blockquote-lg: 0.75rem;
--vf-py-hr: 0.25rem;
// Space between addon and text input
--vf-space-addon: 0;
--vf-space-addon-sm: var(--vf-space-addon);
--vf-space-addon-lg: var(--vf-space-addon);
// Space between checkboxes & radios and their labels
--vf-space-checkbox: 0.375rem;
--vf-space-checkbox-sm: var(--vf-space-checkbox);
--vf-space-checkbox-lg: var(--vf-space-checkbox);
// Space between tags in `TagsElement`
--vf-space-tags: 0.1875rem;
--vf-space-tags-sm: var(--vf-space-tags);
--vf-space-tags-lg: var(--vf-space-tags);
// Space between the field's top and floating label
--vf-floating-top: 0rem;
--vf-floating-top-sm: 0rem;
--vf-floating-top-lg: 0.6875rem;
// Space above `StaticElement` tags
--vf-space-static-tag-1: 1rem;
--vf-space-static-tag-2: 2rem;
--vf-space-static-tag-3: 3rem;
}Input Colors
Background, text, shadow and border color of inputs.
// Input Colors
:root, :before, :after, * {
--vf-bg-input: #ffffff;
--vf-bg-input-hover: var(--vf-bg-input);
--vf-bg-input-focus: var(--vf-bg-input);
--vf-bg-input-danger: var(--vf-bg-input);
--vf-bg-input-success: var(--vf-bg-input);
--vf-bg-checkbox: var(--vf-bg-input);
--vf-bg-checkbox-hover: var(--vf-bg-checkbox);
--vf-bg-checkbox-focus: var(--vf-bg-checkbox);
--vf-bg-checkbox-danger: var(--vf-bg-checkbox);
--vf-bg-checkbox-success: var(--vf-bg-checkbox);
--vf-bg-disabled: var(--vf-gray-200);
--vf-bg-selected: rgba(17,24,39,0.05); // Used eg. when select option is hovered or a checkbox is selected in `blocks` view
--vf-bg-passive: var(--vf-gray-300); // Used as a background color for eg. slider, toggle
--vf-bg-icon: var(--vf-gray-500);
--vf-bg-danger: var(--vf-danger-lighter);
--vf-bg-success: var(--vf-success-lighter);
--vf-bg-tag: var(--vf-primary);
--vf-bg-slider-handle: var(--vf-primary);
--vf-bg-toggle-handle: #ffffff;
--vf-bg-date-head: var(--vf-gray-100);
--vf-bg-addon: transparent;
--vf-bg-btn: var(--vf-primary);
--vf-bg-btn-danger: var(--vf-danger);
--vf-bg-btn-secondary: var(--vf-gray-200);
--vf-color-input: var(--vf-gray-800);
--vf-color-input-hover: var(--vf-color-input);
--vf-color-input-focus: var(--vf-color-input);
--vf-color-input-danger: var(--vf-color-input);
--vf-color-input-success: var(--vf-color-input);
--vf-color-disabled: var(--vf-gray-400);
--vf-color-placeholder: var(--vf-gray-300);
--vf-color-passive: var(--vf-gray-700); // Used when text is displayed on passive background eg. `off` toggle
--vf-color-muted: var(--vf-gray-500); // Used for helper texts eg. element description, floating label
--vf-color-floating: var(--vf-color-muted);
--vf-color-floating-focus: var(--vf-color-floating); // Used when the input is focused
--vf-color-floating-success: var(--vf-color-floating); // Used when the input is filled with success
--vf-color-floating-danger: var(--vf-color-floating); // Used when the input has error
--vf-color-on-primary: #ffffff; // Used when text is displayed on primary color
--vf-color-danger: var(--vf-danger);
--vf-color-success: var(--vf-success);
--vf-color-tag: var(--vf-color-on-primary);
--vf-color-addon: var(--vf-color-input);
--vf-color-date-head: var(--vf-gray-700);
--vf-color-btn: var(--vf-color-on-primary);
--vf-color-btn-danger: #ffffff;
--vf-color-btn-secondary: var(--vf-gray-700);
--vf-border-color-input: var(--vf-gray-300);
--vf-border-color-input-hover: var(--vf-border-color-input);
--vf-border-color-input-focus: var(--vf-primary);
--vf-border-color-input-danger: var(--vf-border-color-input);
--vf-border-color-input-success: var(--vf-border-color-input);
--vf-border-color-checkbox: var(--vf-border-color-input);
--vf-border-color-checkbox-focus: var(--vf-primary);
--vf-border-color-checkbox-hover: var(--vf-border-color-checkbox);
--vf-border-color-checkbox-danger: var(--vf-border-color-checkbox);
--vf-border-color-checkbox-success: var(--vf-border-color-checkbox);
--vf-border-color-checked: var(--vf-primary);
--vf-border-color-passive: var(--vf-gray-300); // Used as a border for passive states eg. `off` toggle
--vf-border-color-slider-tooltip: var(--vf-primary);
--vf-border-color-tag: var(--vf-primary);
--vf-border-color-btn: var(--vf-primary);
--vf-border-color-btn-danger: var(--vf-danger);
--vf-border-color-btn-secondary: var(--vf-gray-200);
--vf-border-color-blockquote: var(--vf-gray-300);
--vf-border-color-hr: var(--vf-gray-300);
}Shadows
Shadows for different component parts.
// Shadows
:root, :before, :after, * {
--vf-shadow-input: 0px 0px 0px 0px rgba(0,0,0,0);
--vf-shadow-input-hover: 0px 0px 0px 0px rgba(0,0,0,0);
--vf-shadow-input-focus: 0px 0px 0px 0px rgba(0,0,0,0);
--vf-shadow-handles: 0px 0px 0px 0px rgba(0,0,0,0);
--vf-shadow-handles-hover: 0px 0px 0px 0px rgba(0,0,0,0);
--vf-shadow-handles-focus: 0px 0px 0px 0px rgba(0,0,0,0);
--vf-shadow-btn: 0px 0px 0px 0px rgba(0,0,0,0);
--vf-shadow-dropdown: 0px 0px 0px 0px rgba(0,0,0,0);
}Input Borders
The widths and radiuses of inputs.
// Input borders
:root, :before, :after, * {
--vf-border-width-input-t: 1px;
--vf-border-width-input-r: 1px;
--vf-border-width-input-b: 1px;
--vf-border-width-input-l: 1px;
--vf-border-width-radio-t: var(--vf-border-width-input-t);
--vf-border-width-radio-r: var(--vf-border-width-input-r);
--vf-border-width-radio-b: var(--vf-border-width-input-b);
--vf-border-width-radio-l: var(--vf-border-width-input-l);
--vf-border-width-checkbox-t: var(--vf-border-width-input-t);
--vf-border-width-checkbox-r: var(--vf-border-width-input-r);
--vf-border-width-checkbox-b: var(--vf-border-width-input-b);
--vf-border-width-checkbox-l: var(--vf-border-width-input-l);
--vf-border-width-dropdown: 1px;
--vf-border-width-btn: 1px;
--vf-border-width-toggle: 0.125rem;
--vf-border-width-tag: 1px;
--vf-border-width-blockquote: 3px;
--vf-radius-input: 0.25rem;
--vf-radius-input-sm: var(--vf-radius-input);
--vf-radius-input-lg: var(--vf-radius-input);
--vf-radius-btn: var(--vf-radius-input);
--vf-radius-btn-sm: var(--vf-radius-input-sm);
--vf-radius-btn-lg: var(--vf-radius-input);
// Used for eg. list button, slider tooltip, info tooltip
--vf-radius-small: var(--vf-radius-input);
--vf-radius-small-sm: var(--vf-radius-input-sm);
--vf-radius-small-lg: var(--vf-radius-input);
// Used for larger inputs eg. textarea, editor, drag and drop, checkbox/radio blocks
--vf-radius-large: var(--vf-radius-input);
--vf-radius-large-sm: var(--vf-radius-input-sm);
--vf-radius-large-lg: var(--vf-radius-input);
--vf-radius-tag: var(--vf-radius-input);
--vf-radius-tag-sm: var(--vf-radius-input-sm);
--vf-radius-tag-lg: var(--vf-radius-input);
--vf-radius-checkbox: var(--vf-radius-input);
--vf-radius-checkbox-sm: var(--vf-radius-input-sm);
--vf-radius-checkbox-lg: var(--vf-radius-input);
--vf-radius-slider: var(--vf-radius-input);
--vf-radius-slider-sm: var(--vf-radius-input-sm);
--vf-radius-slider-lg: var(--vf-radius-input);
--vf-radius-image: var(--vf-radius-input);
--vf-radius-image-sm: var(--vf-radius-input-sm);
--vf-radius-image-lg: var(--vf-radius-input);
--vf-radius-gallery: var(--vf-radius-input);
--vf-radius-gallery-sm: var(--vf-radius-input-sm);
--vf-radius-gallery-lg: var(--vf-radius-input);
}Input Sizes
Sizes of different input fields and their components.
// Input Sizes
:root, :before, :after, * {
--vf-checkbox-size: 1rem;
--vf-checkbox-size-sm: 0.875rem;
--vf-checkbox-size-lg: 1rem;
--vf-gallery-size: 6rem;
--vf-gallery-size-sm: 5rem;
--vf-gallery-size-lg: 7rem;
--vf-toggle-width: 3rem;
--vf-toggle-width-sm: 2.75rem;
--vf-toggle-width-lg: 3rem;
--vf-toggle-height: 1.25rem;
--vf-toggle-height-sm: 1.125rem;
--vf-toggle-height-lg: 1.25rem;
--vf-slider-height: 0.375rem;
--vf-slider-height-sm: 0.3125rem;
--vf-slider-height-lg: 0.5rem;
--vf-slider-height-vertical: 20rem;
--vf-slider-height-vertical-sm: var(--vf-slider-height-vertical);
--vf-slider-height-vertical-lg: var(--vf-slider-height-vertical);
--vf-slider-handle-size: 1rem;
--vf-slider-handle-size-sm: 0.875rem;
--vf-slider-handle-size-lg: 1.25rem;
--vf-slider-tooltip-distance: 0.5rem;
--vf-slider-tooltip-distance-sm: 0.375rem;
--vf-slider-tooltip-distance-lg: 0.5rem;
--vf-slider-tooltip-arrow-size: 0.3125rem;
--vf-slider-tooltip-arrow-size-sm: var(--vf-slider-tooltip-arrow-size);
--vf-slider-tooltip-arrow-size-lg: var(--vf-slider-tooltip-arrow-size);
}Themes
Vueform has a couple of themes tailored to popular CSS frameworks. Themes can be installed out of the box and furhter configured with CSS vars.
Check out available themes.
Tailwind Prefix
Please note that Vueform is only compatible with Tailwind 2 & 3.
If we want to use Tailwind Prefix we need to make some changes to our theme setup.
First we need to import prefix instead of default from Vueform's tailwind or tailwind-material theme and use it as a function with the prefix we want to use:
// vueform.config.js
import { defineConfig } from '@vueform/vueform'
import { prefix as tailwind } from '@vueform/vueform/dist/tailwind'
export default defineConfig({
theme: tailwind('tw-'),
// ...
})After that we need set prefix option in tailwind.config.js:
// tailwind.config.js
module.exports = {
// ...
prefix: 'tw-',
plugins: [
require('@vueform/vueform/tailwind')
]
}Tailwind 2.x
In Tailwind CSS 2.x we need to import @vueform/vueform/tailwind-prefixer and add is as a transform in purge block:
// tailwind.config.js
const prefixer = require('@vueform/vueform/tailwind-prefixer')
module.exports = {
purge: {
// ...
content: [
// ... your existing
'./vueform.config.js', // or where `vueform.config.js` is located
'./node_modules/@vueform/vueform/themes/tailwind/**/*.vue',
'./node_modules/@vueform/vueform/themes/tailwind/**/*.js',
],
transform: {
js: (content) => {
return prefixer(content, 'tw-')
}
}
},
prefix: 'tw-',
plugins: [
require('@vueform/vueform/tailwind')
]
}Tailwind 3.x
In Tailwind CSS 3.x we need to import @vueform/vueform/tailwind-prefixer adn change the content block to an object and add files and transform with the prefixer:
// tailwind.config.js
const prefixer = require('@vueform/vueform/tailwind-prefixer')
module.exports = {
content: {
files: [
// ... your existing
'./vueform.config.js', // or where `vueform.config.js` is located
'./node_modules/@vueform/vueform/themes/tailwind/**/*.vue',
'./node_modules/@vueform/vueform/themes/tailwind/**/*.js',
],
transform: {
js: (content) => {
return prefixer(content, 'tw-')
}
}
},
prefix: 'tw-',
plugins: [
require('@vueform/vueform/tailwind')
]
}Screen Sizes
If we are using different screen sizes than the default ones (sm, md, lg, xl, 2xl) we can pass those as a third argument to prefixer():
js: (content) => {
return prefixer(
content,
'tw-',
['xs', 'sm', 'md', 'lg', 'xl', 'xxl']
)
}Dark Mode
Vueform themes support dark mode out of the box.
By default the dark mode is applied:
- according to
darkModesettings intailwind.config.jswhen using Tailwind CSS - when
.darkclass is present earlier in the HTML tree when using class name based themes.
Vueform's does not set text colors, so make sure to apply dark themes text colors for form elements as well (eg. define
color: whitefor any parent HTML element).
Dark mode is applied by default from version 1.8.0 and can be disabled or configured.
Dark Mode Customization
The dark mode is only an overwrite of certain CSS vars that affect colors.
Colors can be customized by overwriting --vf-dark-* variables after the Vueform theme CSS file or Tailwind CSS is imported.
vueform, material, bootstrap
Customization for vueform, material and bootstrap themes:
/* @vueform/vueform/dist/vueform|material|bootstrap.css is imported before */
.dark, .dark *, .dark :before, .dark :after {
--vf-dark-50: #efefef;
--vf-dark-100: #dcdcdc;
--vf-dark-200: #bdbdbd;
--vf-dark-300: #a0a0a0;
--vf-dark-400: #848484;
--vf-dark-500: #737373;
--vf-dark-600: #393939;
--vf-dark-700: #323232;
--vf-dark-800: #262626;
--vf-dark-900: #191919;
}tailwind
Customization for tailwind theme:
@tailwind base;
@tailwind components;
@tailwind utilities;
.dark, .dark *, .dark :before, .dark :after {
--vf-dark-50: #efefef;
--vf-dark-100: #dcdcdc;
--vf-dark-200: #bdbdbd;
--vf-dark-300: #a0a0a0;
--vf-dark-400: #848484;
--vf-dark-500: #737373;
--vf-dark-600: #393939;
--vf-dark-700: #323232;
--vf-dark-800: #262626;
--vf-dark-900: #191919;
}Detailed Customization
If we want to further customize colors we can use more specific overrides of CSS vars (applies for all themes):
/* You might use a customized CSS selector */
.dark, .dark *, .dark :before, .dark :after {
--vf-bg-input: var(--vf-dark-800);
--vf-bg-input-hover: var(--vf-bg-input);
--vf-bg-input-focus: var(--vf-bg-input);
--vf-bg-input-danger: var(--vf-bg-input);
--vf-bg-input-success: var(--vf-bg-input);
--vf-bg-checkbox: var(--vf-dark-700);
--vf-bg-checkbox-hover: var(--vf-bg-checkbox);
--vf-bg-checkbox-focus: var(--vf-bg-checkbox);
--vf-bg-checkbox-danger: var(--vf-bg-checkbox);
--vf-bg-checkbox-success: var(--vf-bg-checkbox);
--vf-bg-disabled: var(--vf-dark-700);
--vf-bg-selected: var(--vf-dark-700); /* Used eg. when select option is hovered or a checkbox is selected in `blocks` view */
--vf-bg-passive: var(--vf-dark-700); /* Used as a background color for eg. slider, toggle */
--vf-bg-icon: var(--vf-dark-400);
--vf-bg-danger: var(--vf-danger-lighter);
--vf-bg-success: var(--vf-success-lighter);
--vf-bg-addon: transparent;
--vf-bg-tag: var(--vf-primary);
--vf-bg-slider-handle: var(--vf-primary);
--vf-bg-toggle-handle: #ffffff;
--vf-bg-date-head: var(--vf-dark-700);
--vf-bg-btn: var(--vf-primary);
--vf-bg-btn-danger: var(--vf-danger);
--vf-bg-btn-secondary: var(--vf-dark-700);
--vf-color-on-primary: #ffffff; /* Used when text is displayed on primary color */
--vf-color-input: var(--vf-dark-100);
--vf-color-input-hover: var(--vf-color-input);
--vf-color-input-focus: var(--vf-color-input);
--vf-color-input-danger: var(--vf-color-input);
--vf-color-input-success: var(--vf-color-input);
--vf-color-placeholder: var(--vf-dark-500);
--vf-color-disabled: var(--vf-dark-500);
--vf-color-passive: var(--vf-dark-900); /* Used when text is displayed on passive background eg. `off` toggle */
--vf-color-muted: var(--vf-dark-500); /* Used for helper texts eg. element description, floating label */
--vf-color-floating: var(--vf-color-muted);
--vf-color-floating-focus: var(--vf-color-floating); /* Used when the input is focused */
--vf-color-floating-success: var(--vf-color-floating); /* Used when the input is filled with success */
--vf-color-floating-danger: var(--vf-color-floating); /* Used when the input has error */
--vf-color-danger: var(--vf-danger);
--vf-color-success: var(--vf-success);
--vf-color-addon: initial;
--vf-color-tag: var(--vf-color-on-primary);
--vf-color-date-head: var(--vf-dark-200);
--vf-color-btn: var(--vf-color-on-primary);
--vf-color-btn-danger: #ffffff;
--vf-color-btn-secondary: var(--vf-dark-300);
--vf-border-color-input: var(--vf-dark-800);
--vf-border-color-input-focus: var(--vf-primary);
--vf-border-color-input-hover: var(--vf-border-color-input);
--vf-border-color-input-danger: var(--vf-border-color-input);
--vf-border-color-input-success: var(--vf-border-color-input);
--vf-border-color-checkbox: var(--vf-border-color-input);
--vf-border-color-checkbox-focus: var(--vf-primary);
--vf-border-color-checkbox-hover: var(--vf-border-color-checkbox);
--vf-border-color-checkbox-danger: var(--vf-border-color-checkbox);
--vf-border-color-checkbox-success: var(--vf-border-color-checkbox);
--vf-border-color-checked: var(--vf-primary);
--vf-border-color-btn: var(--vf-primary);
--vf-border-color-tag: var(--vf-primary);
--vf-border-color-slider-tooltip: var(--vf-primary);
--vf-border-color-passive: var(--vf-dark-700); /* Used as a border for passive states eg. `off` toggle */
--vf-border-color-btn-danger: var(--vf-danger);
--vf-border-color-btn-secondary: var(--vf-dark-700);
--vf-border-color-blockquote: var(--vf-dark-700);
--vf-border-color-hr: var(--vf-dark-700);
--vf-shadow-input: 0px 0px 0px 0px rgba(0,0,0,0);
--vf-shadow-input-hover: 0px 0px 0px 0px rgba(0,0,0,0);
--vf-shadow-input-focus: 0px 0px 0px 0px rgba(0,0,0,0);
--vf-shadow-handles: 0px 0px 0px 0px rgba(0,0,0,0);
--vf-shadow-handles-hover: 0px 0px 0px 0px rgba(0,0,0,0);
--vf-shadow-handles-focus: 0px 0px 0px 0px rgba(0,0,0,0);
--vf-shadow-btn: 0px 0px 0px 0px rgba(0,0,0,0);
--vf-shadow-dropdown: 0px 0px 0px 0px rgba(0,0,0,0);
}These are all the color related CSS variables in Vueform, but we might change others if we see fit.
Disabling Dark Mode
Dark mode is only available from version 1.8.0 but can be disabled.
vueform, bootstrap, material
In class name based themes the dark theme can be disabled by importing the css without dark mode variables:
// eg. vueform.config.js
import '@vueform/vueform/dist/{THEME_NAME}.nodark.css'tailwind
The dark theme can be disabled in tailwind theme by setting vfDarkMode to false in tailwind.config.js:
// tailwind.config.js
export default {
// ...
vfDarkMode: false,
}Configuring Dark Mode
We can configure how and when dark mode should be enabled.
vueform, bootstrap, material
When using a class name based theme by default the .dark class must is present earlier in the HTML tree (eg. <body> or <html>).
The class name can be customized by importing the theme's .scss file instead of .css and using the vf-dark-vars mixin to load CSS vars:
<!-- Eg. App.vue -->
<style lang="scss">
@import '@vueform/vueform/themes/vueform/scss/index.scss';
@media (prefers-color-scheme: dark) {
:root, :before, :after, * {
@include vf-dark-vars;
}
}
</style>tailwind
When using Tailwind CSS the dark mode will be automatically detected based on the settings in tailwind.config.js.
If darkMode is:
- not set, user preferences will be used using
@media (prefers-color-scheme: dark)query. - set to
classthe.darkclass must is present earlier in the HTML tree. - an array the second item will be the class selector.
Learn more about Tailwind CSS dark mode configuration here.
