Skip to content

TextEditor

The <TextEditor> is a WYSIWYG (What You See Is What You Get) editor - An editor that allows users to create and edit rich text content in a form that resembles its appearance when displayed as a finished product.

It leverages Quill.js as the underlying editor, which is a powerful, extensible, and a customizable rich text editor.

Basic Usage

Diplayed content

      
    
vue
<script setup lang="ts">
  import { defineAsyncComponent, ref } from 'vue';

  // Since Quill uses the browser api, defineAsyncComponent can lazy load the Text Editor in a non SSR environment
  const TextEditor = defineAsyncComponent(() => import('../../../src/components/TextEditor/TextEditor.vue'));

  const modelValue = ref('');
</script>

<template>
  <TextEditor
    id="text-editor-basic"
    v-model="modelValue"
    label="Description"
    placeholder="Insert a product description"
  />

  <div class="tw-mt-2">
    <p class="">Diplayed content</p>

    <pre class="tw-text-wrap">
      {{ modelValue }}
    </pre>
  </div>
</template>

Placeholder

The placeholder is a text that appears in the editor when it is empty. Quill.js adds the placeholder as a content property of a ::before pseudo-element, but you can change its text with the placeholder prop.

vue
<script setup lang="ts">
  import { defineAsyncComponent, ref } from 'vue';

  // Since Quill uses the browser api, defineAsyncComponent can lazy load the Text Editor in a non SSR environment
  const TextEditor = defineAsyncComponent(() => import('../../../src/components/TextEditor/TextEditor.vue'));

  const modelValue = ref('');
</script>

<template>
  <TextEditor id="text-editor-placeholder" v-model="modelValue" placeholder="Product description" />
</template>

Field props

The <TextEditor> component uses the Field component internally, so you can pass most of the field properties to it; the Field props available for use are listed below.

Label

Adds a label above the text editor.

vue
<script setup lang="ts">
  import { defineAsyncComponent, ref } from 'vue';

  // Since Quill uses the browser api, defineAsyncComponent can lazy load the Text Editor in a non SSR environment
  const TextEditor = defineAsyncComponent(() => import('../../../src/components/TextEditor/TextEditor.vue'));

  const modelValue = ref('');
</script>

<template>
  <TextEditor id="text-editor-label" v-model="modelValue" label="Product description" />
</template>

Disabled

The is-disabled prop disables the editor and prevents the user from interacting with it.

WARNING

If the user cannot take any action to make the field editable, the is-read-only prop should be used instead.

See Disabled vs read-only for details.

INFO

Dev Note: Quill.js sets the editor's contenteditable to false when isDisabled is true to prevent the user from editing the content.

vue
<script setup lang="ts">
  import { defineAsyncComponent, ref } from 'vue';

  // Since Quill uses the browser api, defineAsyncComponent can lazy load the Text Editor in a non SSR environment
  const TextEditor = defineAsyncComponent(() => import('../../../src/components/TextEditor/TextEditor.vue'));

  const modelValue = ref('');
</script>

<template>
  <TextEditor
    id="text-editor-read-only"
    v-model="modelValue"
    label="Product description"
    placeholder="Insert a product description"
    is-disabled
  />
</template>

Read only

Styles the field as read-only. This is useful when displaying the editor rich text because it will have all the same styles and spacing of a regular field.

INFO

Quill.js sets the editor's contenteditable to false when isReadOnly is true to prevent the user from editing the content.

vue
<script setup lang="ts">
  import { defineAsyncComponent, ref } from 'vue';

  // Since Quill uses the browser api, defineAsyncComponent can lazy load the Text Editor in a non SSR environment
  const TextEditor = defineAsyncComponent(() => import('../../../src/components/TextEditor/TextEditor.vue'));

  const modelValue = ref(`
    <p><strong>Quality Glass, For Every Stoner</strong></p>
    <p></br></p>
    <p>Thick Ass Glass (TAG) encompasses the meaning of high quality affordable borosilicate. <strong>These pieces can take a beating while delivering smooth hits from even the biggest clouds</strong>.</p>
    <p></br></p>
    <p>The TAG logo is sand-blasted on the neck of the bong.</p>
    <p></br></p>
    <ul>
      <li>10" Bent Neck</li>
      <li>Matrix Diffuser</li>
      <li>65x5MM</li>
      <li>14mm Female Down Stem</li>
    </ul>
  `);
</script>

<template>
  <TextEditor id="text-editor-read-only" v-model="modelValue" label="Product description" is-read-only />
</template>

Add bottom space

Adds spacing under the field that is consistent whether hint/error text is displayed.

vue
<script setup lang="ts">
  import { defineAsyncComponent, ref } from 'vue';

  import Button from '../../../src/components/Button/Button.vue';
  import Input from '../../../src/components/Input/Input.vue';

  // Since Quill uses the browser api, defineAsyncComponent can lazy load the Text Editor in a non SSR environment
  const TextEditor = defineAsyncComponent(() => import('../../../src/components/TextEditor/TextEditor.vue'));

  const modelValue = ref('');
  const bottomSpace = ref(true);
</script>

<template>
  <Button class="tw-mb-6" @click="bottomSpace = !bottomSpace">Toggle bottom space</Button>

  <TextEditor
    id="text-editor-bottom-space"
    v-model="modelValue"
    label="Description"
    placeholder="Insert a product description"
    class="tw-mb-2"
    :add-bottom-space="bottomSpace"
  />

  <Input label="Product Category" :add-bottom-space="bottomSpace" />
</template>

Required

The required prop adds a red asterisk next to the label.

vue
<script setup lang="ts">
  import { defineAsyncComponent, ref } from 'vue';

  // Since Quill uses the browser api, defineAsyncComponent can lazy load the Text Editor in a non SSR environment
  const TextEditor = defineAsyncComponent(() => import('../../../src/components/TextEditor/TextEditor.vue'));

  const modelValue = ref('');
</script>

<template>
  <TextEditor id="text-editor-hint-text" v-model="modelValue" label="Product description" is-required />
</template>

Optional

The show-optional-in-label prop displays (optional) to the right of the label text.

vue
<script setup lang="ts">
  import { defineAsyncComponent, ref } from 'vue';

  // Since Quill uses the browser api, defineAsyncComponent can lazy load the Text Editor in a non SSR environment
  const TextEditor = defineAsyncComponent(() => import('../../../src/components/TextEditor/TextEditor.vue'));

  const modelValue = ref('');
</script>

<template>
  <TextEditor id="text-editor-hint-text" v-model="modelValue" label="Product description" show-optional-in-label />
</template>

Hint text

Displays the provided text below the TextEditor. See more about this property in the hint-text section of the Field component.

vue
<script setup lang="ts">
  import { defineAsyncComponent, ref } from 'vue';

  // Since Quill uses the browser api, defineAsyncComponent can lazy load the Text Editor in a non SSR environment
  const TextEditor = defineAsyncComponent(() => import('../../../src/components/TextEditor/TextEditor.vue'));

  const modelValue = ref('');
</script>

<template>
  <TextEditor id="text-editor-hint-text" v-model="modelValue" hint-text="The product description" />
</template>

Error Text

Displays the provided text in red below the input. See more about this property in the error-text section of the Field component.

INFO

The hint-text will be replaced by the error-text if both are provided.

vue
<script setup lang="ts">
  import { defineAsyncComponent, Ref, ref } from 'vue';

  import Button from '../../../src/components/Button/Button.vue';
  import useValidation, { isDefined } from '../../../src/composables/useValidation/useValidation';
  import { ValidationRules } from '../../../src/composables/useValidation/useValidation.types';
  import { t } from '../../../src/locale';

  // Since Quill uses the browser api, defineAsyncComponent can lazy load the Text Editor in a non SSR environment
  const TextEditor = defineAsyncComponent(() => import('../../../src/components/TextEditor/TextEditor.vue'));

  interface FormState {
    description?: string;
  }

  const formState: Ref<FormState> = ref(getInitialFormState());

  function getInitialFormState() {
    return {
      description: undefined,
    };
  }

  const validationRules: ValidationRules<FormState> = {
    description: [
      {
        name: 'required',
        validator(value) {
          return isDefined(value) && value !== '<p><br></p>';
        },
        message: t('ll.validation.required'),
      },
    ],
  };

  const validation = useValidation({ rules: validationRules, values: formState });

  async function onSubmit() {
    await validation.validate();

    if (validation.hasErrors) {
      return;
    }

    alert(`Form submitted\n ${JSON.stringify(formState.value, null, 2)}`);
  }
</script>

<template>
  <form @submit.prevent>
    <TextEditor
      id="text-editor-validation"
      v-model="formState.description"
      :error-text="validation.getError('description')"
      label="Product template"
      hint-text="The product description"
      add-bottom-space
      @blur="validation.touch('description')"
    />
    <Button class="tw-mt-2" type="button" @click="onSubmit">Submit</Button>
  </form>
</template>

Controls

The controls prop can be a set of format bar buttons which can be either a list of single controls or a list of a grouped control arrays.

See the Quill.js documentation for more information about the available controls.

INFO

For a design reasons, the <TextEditor> component only supports this small list of controls: bold, underline, italic, list (bullet, ordered), link, divider.

Simple controls

vue
<script setup lang="ts">
  import { defineAsyncComponent, ref } from 'vue';

  import { TextEditorControls } from '../../../src/components/TextEditor/TextEditor.vue';

  // Since Quill uses the browser api, defineAsyncComponent can lazy load the Text Editor in a non SSR environment
  const TextEditor = defineAsyncComponent(() => import('../../../src/components/TextEditor/TextEditor.vue'));

  const modelValue = ref('');
  const controls: TextEditorControls = ['bold', 'italic'];
</script>

<template>
  <TextEditor id="text-editor-controls" v-model="modelValue" :controls="controls" />
</template>

Grouped controls

The controls props can receive a matrix indicating that each matrix row is a grouped set of controls like:

js
[
  ['bold', 'italic', 'underline'],
  ['link'],
  ['divider']
]
vue
<script setup lang="ts">
  import { defineAsyncComponent, ref } from 'vue';

  import { TextEditorControls } from '../../../src/components/TextEditor/TextEditor.vue';

  // Since Quill uses the browser api, defineAsyncComponent can lazy load the Text Editor in a non SSR environment
  const TextEditor = defineAsyncComponent(() => import('../../../src/components/TextEditor/TextEditor.vue'));

  const modelValue = ref('');
  const controls: TextEditorControls = [
    ['bold', 'italic'],
    [{ list: 'bullet' }, { list: 'ordered' }],
  ];
</script>

<template>
  <TextEditor id="text-editor-grouped-controls" v-model="modelValue" :controls="controls" />
</template>

Control handlers

The handlers prop can receive an object mapper with custom control handlers which will dictate each control behavior upon click.

vue
<script setup lang="ts">
  import { defineAsyncComponent, ref } from 'vue';

  import { TextEditorControlHandlerMap } from '../../../src/components/TextEditor/TextEditor.vue';

  // Since Quill uses the browser api, defineAsyncComponent can lazy load the Text Editor in a non SSR environment
  const TextEditor = defineAsyncComponent(() => import('../../../src/components/TextEditor/TextEditor.vue'));

  const modelValue = ref('');

  const handlers: TextEditorControlHandlerMap = {
    bold: () => alert('Bold clicked'),
    italic: () => alert('Italic clicked'),
  };
</script>

<template>
  <TextEditor id="text-editor-control-handlers" v-model="modelValue" :handlers="handlers" />
</template>

API

See the documentation below for a complete reference to all the props and classes available to the components mentioned here.