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>

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>

Disabled

The isDisabled prop disables the editor and prevents the user from interacting with it.

INFO

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>

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.