Style & Theme

#Binding CSS Classes

To add CSS classes to an element simply add class property to it's schema and you will have that on the most outer container the element template has.

schema: {
  name: {
    type: 'text',
    label: 'Name',
    class: 'name-element'
  }
}

Why are we using the term most outer? Because each theme provides their own layouts and templates for elements while all of them have in common that the class defined by the user in schema must be placed at the most outer container the element has.

Let's see a very simplified example of to illustrate this:

// TextElement's template

<template>
  <div class="element-container" :class="schema.class">
    <div class="element-inner">
      <div class="label-container">
        <label :for="name">{{ label }}</label>
      </div>
      <div class="field-container">
        <input v-model="model" type="text" />
      </div>
    </div>
  </div>
</template>

You can see that this is a very simplified TextElement template, where the element-container receives the class defined in schema.

While this is a simple and easy solution if we want to add a class only to the element container, but what if we want to specify a class for let's say the label-container or field-container? See how we can do that.

#Additional Classes

Themes, as you will see in the next section, contain a predefined list of classes that components use. In the previous example we've used static classes within the component's template, but in fact they are dynamically assigned. So that's how are template from above actually looks like:

// TextElement's template

<template>
  <div :class="[theme.classes.elementContainer, schema.class]">
    <div :class="theme.classes.elementInner">
      <div :class="theme.classes.labelContainer">
        <label :for="name">{{ label }}</label>
      </div>
      <div :class="theme.classes.fieldContainer">
        <input v-model="model" type="text" />
      </div>
    </div>
  </div>
</template>

Using this method you can override template classes on the flight, using the classes option of an element:

schema: {
  name: {
    type: 'text',
    label: 'Name',
    classes: {
      elementContainer: 'name-element-container',
      fieldContainer: 'name-element-field',
    }
  }
}

So now instead of the theme's default elementContainer and fieldContainer CSS class the ones defined for the element will be used.

#Using Themes

A theme is a set of variables defined in its index.js and styles in a theme.scss. Themes are registered in Configuration and can be selected for a form using theme property:

// This form will use the 'bs4' registered theme

export default {
  mixins: [Laraform],
  data: () => ({
    theme: 'bs4', 
    schema: {
      // ...
    }
  })
}

#Theme File

Each theme has an index.js called the theme file in it's root directory which exports an object, which is called the theme object:

// ./path/to/theme/index.js

export default {
  classes: {},    // CSS class names the components use
  elements: {},   // element components
  components: {}, // components of the form
  layouts: {},    // definition of the framework's layouts
  grid: {},       // definition of the framework's grid system
}

As you can see it has several properties, so let's go through each of them.

'classes' Property

The classes property contains a list of CSS classes that components use. This means that the CSS class names of components are not typed in their actual templates but referenced from the theme's classes object. Let's see what we mean by that. Here we have a theme file with several class names defined:

// ./path/to/theme/index.js

export default {
  classes: {
    elementContainer: 'element-container',
    elementInner: 'element-inner',
    labelContainer: 'label-container',
    fieldContainer: 'field-container',
  },
  // ...
}

Now as each component in Laraform have access to an injected property called theme, this is how a simplified TextElement template would look like:

// TextElement's template

<template>
  <div :class="[theme.classes.elementContainer]">
    <div :class="theme.classes.elementInner">
      <div :class="theme.classes.labelContainer">
        <label :for="name">{{ label }}</label>
      </div>
      <div :class="theme.classes.fieldContainer">
        <input v-model="model" type="text" />
      </div>
    </div>
  </div>
</template>

As you can see, CSS classes are referenced from the theme file via injected theme property.

'elements' Property

Each component within Laraform consist of two different files. The first contains the <script> part of a single file component and placed in Laraform's /components/ folder, while the second is responsible for having the <template> and defined by the theme. Let's see how does that look like:

// laraform/src

.
├── index.js
├── components // core components (scripts only)
|   ├── elements
|   |   ├── HiddenElement.js
|   |   ├── TextElement.js
|   |   └── ...
|   ├── FormButtons.js
|   ├── FormTabs.js
|   └── ...
├── themes
|   ├── bs4
|   |   ├── components // component views (templates only)
|   |   |   ├── elements
|   |   |   |   ├── HiddenElement.vue
|   |   |   |   ├── TextElement.vue
|   |   |   |   └── ...
|   |   |   ├── FormButtons.vue
|   |   |   ├── FormTabs.vue
|   |   |   └── ...
|   └── index.js
└── ...

As you can see within /components/ directory there are the core files which have the <script> parts and then there are views in /themes/bs4/components/ which contains the <template> unique for the theme.

This way business logic and views can be completely separated for each component and different themes can define different views for the same components.

The elements property of the theme file contains the list of theme element views provided by the theme:

// ./path/to/theme/index.js

import HiddenElement from './components/elements/HiddenElement.vue'
import TextElement from './components/elements/TextElement.vue'

export default {
  // ...
  elements: {
    HiddenElement,
    TextElement,
    // ...
  },
  // ...
}

'components' Property

Just like elements, other components also have their views defined uniquely by the theme as you've seen from the previous file structure.

The components property is to register all the other components which are not elements:

// ./path/to/theme/index.js

import FormButtons from './components/FormButtons.vue'
import FormTabs from './components/FormTabs.vue'

export default {
  // ...
  components: {
    FormButtons,
    FormTabs,
    // ...
  },
  // ...
}

'layouts' Property

A theme might have different form layouts, like Bootstrap have inline or horizontal. Different layout identifiers and their class names can be defined here, which later can be used by the form's layout property:

// ./path/to/theme/index.js

export default {
  // ...
  layouts: {
    inline: 'form-inline',
    horizontal: 'form-horizontal'
  },
  // ...
}

'grid' Property

If the CSS framework the theme is using a grid system then it's variables are defined here:

// ./path/to/theme/index.js

export default {
  // ...
  grid: {
    // Maximum number of columns.
    // Leave blank if columns are not
    // specified by numbers.
    columns: 12,

    // List of available breakpoints.
    breakpoints: ['xl', 'lg', 'md', 'sm', 'xs'],

    // Default breakpoint which will be used
    // if the user does not specify breakpoints
    // for column sizes.
    defaultBreakpoint: 'xs',

    // Class name template for columns;
    // `:` represents a variable.
    column: 'col-:breakpoint-:size',
  }
  // ...
}

#Theme Styles

The CSS styles of a theme is defined within it's theme.scss, called the theme style. This file is usually using variables that the CSS framework the theme is meant for uses, like $primary in case of Bootstrap. Therefore the theme.scss must be included in you app's .scss file where you include the CSS framework and has acccess to the variables.

For example:


// app.scss

// Fonts
@import url('https://fonts.googleapis.com/css?family=Nunito');

// Variables
@import 'variables';

// Bootstrap
@import '~bootstrap/scss/bootstrap';

// Laraform
@import '~laraform/src/themes/bs4/scss/theme'

Theme Variables

Each theme uses variables used across it's styles. Here's the list of these:

//== Genaral
//
//## Font sizes, colors, backgrounds and more.

$lf-primary-color: #24ACF8 !default;
$lf-danger-color: #bf5329 !default;
$lf-black-color: #000000 !default;
$lf-disabled-color: #9BB2BF !default;

$lf-text-color: #333333 !default;
$lf-font-size: 14px !default;
$lf-line-height: 1.6 !default;

$lf-gutter: 30px !default;
$lf-padding-base-horizontal: 12px !default;
$lf-padding-base-vertical: 6px !default;
$lf-form-group-margin-bottom: 15px !default;
$lf-border-radius: 4px !default;

$lf-passive-background: #d6e4ec !default;
$lf-passive-background-lighter: #e9eef1 !default;

$lf-input-font-size: 14px !default;
$lf-input-color: #555555 !default;
$lf-input-placeholder-color: #999999 !default;
$lf-input-bg: #ffffff !default;
$lf-input-bg-disabled: #eeeeee !default;
$lf-input-border: #cccccc !default;
$lf-input-border-focus: #66afe9 !default;
$lf-input-border-radius: $lf-border-radius !default;
$lf-input-height: 36px !default;

$lf-button-color: #636b6f !default;

$lf-cursor-disabled: not-allowed !default;

//== Date, datetime calendar
//
//## Width, radius, height

$lf-calendar-width: 307.875px !default;
$lf-calendar-date-radius: 50% !default;
$lf-calendar-date-height: 39px !default;

//== Uploader
//
//## Drag n drop, previews, icons

//** Drag 'n drop

$lf-dnd-background: #fafafa !default;
$lf-dnd-padding: 30px 30px !default;

$lf-dnd-border: 1px dashed #e7e7e7 !default;
$lf-dnd-border-hover: 1px dashed $lf-primary-color !default;
$lf-dnd-border-over: 2px dashed $lf-primary-color !default;
$lf-dnd-border-radius: 6px !default;

$lf-dnd-font-size: $lf-font-size !default;
$lf-dnd-font-size-title: $lf-font-size !default;
$lf-dnd-text-color: $lf-text-color !default;
$lf-dnd-text-color-title: $lf-text-color !default;

$lf-dnd-icon-color: #{str-replace(#{$lf-primary-color}, '#', '%23')} !default;

//** Preview

$lf-preview-margin-top: 15px !default;
$lf-preview-gallery-margin-right: 10px !default;

$lf-preview-ghost-background: #f1f1f1 !default;
$lf-preview-ghost-border-radius: $lf-border-radius !default;

$lf-preview-image-background: #fafafa !default;

$lf-preview-list-image-width: 64px !default;
$lf-preview-list-image-height: 64px !default;
$lf-preview-list-border: 1px solid #cccccc !default;
$lf-preview-list-border-radius: 5px !default;
$lf-preview-list-padding: 12px 12px !default;

$lf-preview-gallery-image-width: 80px !default;
$lf-preview-gallery-image-height: 80px !default;
$lf-preview-gallery-border: 1px solid #cccccc !default;
$lf-preview-gallery-border-radius: 5px !default;
$lf-preview-gallery-padding: 8px !default;
$lf-preview-gallery-width: 98px !default;

$lf-preview-avatar-silhouette-color: '%23CBE3F5' !default;
$lf-preview-avatar-placeholder-background-color: #EEF8FF !default;

//** Icons

$lf-file-remove-icon-color: #{str-replace(#{$lf-primary-color}, '#', '%23')} !default;
$lf-file-remove-icon-background-color: transparent !default;
$lf-file-remove-icon-background-color-hover: lighten($lf-primary-color, 40%) !default;
$lf-file-remove-icon-border-radius: 3px !default;

$lf-list-image-remove-icon-color: #{str-replace(#{$lf-input-color}, '#', '%23')} !default;
$lf-list-image-remove-icon-background-color: transparent !default;
$lf-list-image-remove-icon-background-color-hover: #f1f1f1 !default;
$lf-list-image-remove-icon-border-radius: 3px !default;

$lf-gallery-image-remove-icon-color: '%23ffffff' !default;
$lf-gallery-image-remove-icon-background-color: rgba(0,0,0,0.45) !default;
$lf-gallery-image-remove-icon-background-color-hover: rgba(0,0,0,0.7) !default;
$lf-gallery-image-remove-icon-border-radius: 0 4px 0 3px !default;

$lf-avatar-image-remove-icon-color: '%23ffffff' !default;
$lf-avatar-image-remove-icon-background-color: rgba(0,0,0,0.45) !default;
$lf-avatar-image-remove-icon-background-color-hover: rgba(0,0,0,0.7) !default;
$lf-avatar-image-remove-icon-border-radius: 4px !default;

$lf-upload-icon-color: #{str-replace(#{$lf-button-color}, '#', '%23')} !default;
$lf-clip-icon-color: #{str-replace(#{$lf-black-color}, '#', '%23')} !default;

//== Wizard
//
//## Padding, margins, backgrounds

$lf-wizard-margin: 0 0 30px 0 !default;
$lf-wizard-padding: 30px 0 !default;
$lf-wizard-border-bottom: 1px solid #e7e7e7 !default;

$lf-wizard-active-step-circle-background: #ffffff !default;

Each theme has these variables overwritten by it's own values, often using CSS framework based values, for example:


// ./path/to/bs4/scss/theme.scss

//== Genaral
//
//## Font sizes, colors, backgrounds and more.

$lf-primary-color: $primary !default;

You can override these variables at your project's .scss file:


// app.scss

// Fonts
@import url('https://fonts.googleapis.com/css?family=Nunito');

// Variables
@import 'variables';

// Bootstrap
@import '~bootstrap/scss/bootstrap';

// Laraform
@import '~laraform/src/themes/bs4/scss/theme'

// Overriding Laraform Bootstrap 4 theme variables
$lf-primary-color: #00aaff;

#Available Themes

Default

If you are not using a supported CSS framework you might want to go with our default theme, which does not have any dependency.

You can reach the theme here:

  • laraform/src/themes/default/index.js
  • ~laraform/src/themes/default/scss/theme.scss

Bootstrap 3

If you are using Bootstrap 3, you can use Laraform's default bs3 theme to render your form compatible with the framework.

You can reach the theme here:

  • laraform/src/themes/bs3/index.js
  • ~laraform/src/themes/bs3/scss/theme.scss

Bootstrap 4

If you are using Bootstrap 4, you can use Laraform's default bs4 theme to render your form compatible with the framework.

You can reach the theme here:

  • laraform/src/themes/bs4/index.js
  • ~laraform/src/themes/bs4/scss/theme.scss

#Custom Theme

You can easily create your custom theme by extending an existing one like Bootstrap 3/4 or the default one, which does not depend on any CSS framework.

#Custom Theme File

Create a new index.js and add the following contents:

// ./path/to/my/theme/index.js

import { extendTheme } from '@laraform/laraform/src/utils'
import defaultTheme from '@laraform/laraform/src/themes/default'

export default extendTheme(defaultTheme, {
  classes: {},
  elements: {},
  components: {},
  layouts: {},
  grid: {},
})

In this example we are extending the default Laraform theme which is not related to any CSS framework, but you could do the same for any other existing themes, like Bootstrap 4 by importing 'laraform/src/themes/bs4' instead of the default.

Now you can start adding your own properties to the theme file, for example overriding classes:

// ./path/to/my/theme/index.js

import { extendTheme } from '@laraform/laraform/src/utils'
import defaultTheme from '@laraform/laraform/src/themes/default'

export default extendTheme(defaultTheme, {
  classes: {
    elementPrefix: 'el-',
    elementContainer: 'my-element-container',
    element: 'my-element',
    elementInner: 'my-element-inner',
    // ..
  },
  elements: {},
  components: {},
  layouts: {},
  grid: {},
})

Registering The Theme File

To register your newly created theme file, simply call .theme() before installing Laraform:

import Vue from 'vue'
import Laraform from '@laraform/laraform'
import MyTheme from './path/to/my/theme'

Laraform.theme('my-theme', MyTheme)

Vue.use(Laraform)

const app = new Vue({
  el: '#app'
})

That's it, now you can use your theme at any of your form instances:

export default {
  mixins: [Laraform],
  data: () => ({
    theme: 'my-theme',
    schema: {
      // ...
    }
  })
}

#Custom Theme Styles

To extend an existing theme's style create a new .scss file with the following contents:


.my-theme {
  @import '~laraform/src/themes/default/scss/vars';
  @import '~laraform/src/themes/default/scss/mixins';
  @import '~laraform/src/themes/default/scss/components';
}

As you can see in this example we are extending the default theme, but you could do the same with bs3 or b4 for example, by replaceing default in import paths.

Now you are free to add any custom styles in .my-theme class, which will be the CSS class of your main form:


// ./path/to/my/theme/scss/theme.scss

.my-theme {
  @import '~laraform/src/themes/default/scss/vars';
  @import '~laraform/src/themes/default/scss/mixins';
  @import '~laraform/src/themes/default/scss/components';

  $lf-primary-color: #00aaff;

  background-color: #f5f7f9;
}

If you do not wish to use any of the theme's existing styles you might leave importing any of the theme scss files.

After creating your theme style, you need to register the .scss in your app main stylesheet, where it can access CSS framework variables (if any used):

// app.scss

// Fonts
@import url('https://fonts.googleapis.com/css?family=Nunito');

// Variables
@import 'variables';

// Bootstrap
@import '~bootstrap/scss/bootstrap';

// Laraform
@import './path/to/my/theme/scss/theme.scss'

The last thing you need to do is to overwrite the form property in the theme file's classes object, so that my-theme class will be used for form element:

// ./path/to/my/theme/index.js

import { extendTheme } from '@laraform/laraform/src/utils'
import defaultTheme from '@laraform/laraform/src/themes/default'

export default extendTheme(defaultTheme, {
  classes: {
    form: 'my-theme'
  },
  elements: {},
  components: {},
  layouts: {},
  grid: {},
})

#Overriding Components

If you created a new theme as described previously, your theme is almost identical to the default theme as both theme file and theme styles were extended from it. This means that all the elements and components are rendered with default views as they are not overridden by your theme. In this section we will see how you can replace any of them.

For a very basic example let's see how we could have a custom TextElement:

// ./path/to/my/theme/components/elements/TextElement.vue

<template>
  <div class="my-text-element">
    <label
      :for="name"
    >{{ label }}</label>

    <input
      v-model="model"
      :type="inputType"
    />
  </div>
</template>

<script>
  import TextElement from '@laraform/laraform/src/components/elements/TextElement'

  export default {
    mixins: [TextElement]
  }
</script>

As you can see we've imported the TextElement core element from Laraform and used it as a mixin for our own view implemantation. Properties like name, label, inputType and model can be used because we are extending the core TextElement which has the <script> part of the component and therefore its API can be used.

Now to override the TextElement provided by the default theme, we need to let the theme know that we want to use our custom component for TextElement and therefore we need to add it in the theme file:

// ./path/to/my/theme/index.js

import { extendTheme } from '@laraform/laraform/src/utils'
import defaultTheme from '@laraform/laraform/src/themes/default'

import TextElement from './components/elements/TextElement.vue'

export default extendTheme(defaultTheme, {
  classes: {},
  elements: {
    TextElement
  },
  components: {},
  layouts: {},
  grid: {},
})

From now on, when you are using a text type element within a form using my-theme as theme, this template will be rendered.

Including Other Components

You might need to use already existing components in your component, like ElementLabel, but this requires a bit of a trick. Instead of importing the component and using it directly we can access it via the theme's components objects:

// ./path/to/my/theme/components/elements/TextElement.vue

<template>
  <div class="my-text-element">
    <component
      :is="theme.components.ElementLabel"
      :for="id"
      v-html="label"
    />

    <input
      v-model="model"
      :type="inputType"
    />
  </div>
</template>

<script>
  import TextElement from '@laraform/laraform/src/components/elements/TextElement'

  export default {
    mixins: [TextElement]
  }
</script>

This way components used within the components are becoming an easily interchangable by simply overriding theme in the theme file:

// ./path/to/my/theme/index.js

import { extendTheme } from '@laraform/laraform/src/utils'
import defaultTheme from '@laraform/laraform/src/themes/default'

import MyCustomElementLabel from './components/MyCustomElementLabel.vue'

export default extendTheme(defaultTheme, {
  classes: {},
  elements: {},
  components: {
    ElementLabel: MyCustomElementLabel
  },
  layouts: {},
  grid: {},
})