Quill

Allow users to create rich text content with the Quill editor.

Getting Started

To get started, you can install the package with the following command:

npm install @vueup/vue-quill@latest --save

Usage

Basic

Here is a basic example of how to use the Quill component. We are using a technique called Slot Forwarding so that if the developer wants to create a component and pass through the toolbar slot, they can do so.

Toolbar

We can add our custom toolbar configuration by using the toolbar prop.

Slot - Toolbar

Another way of customizing the toolbar is by using the toolbar slot. This way, we can create a custom toolbar with our own components.

Bubble Theme

We can pass the bubble value to the theme prop to use the snow theme.

You have to select something in the editor to see the toolbar.

Module

We can pass an object or an array of objects to the module prop to use any Quill module.

Something like this:

import BlotFormatter from "quill-blot-formatter";

type SingleModule = {
  name: string;
  module: any;
  options?: any;
};
type ModuleObject = SingleModule | SingleModule[];

const modules: ModuleObject = {
  name: "blotFormatter",
  module: BlotFormatter,
  options: {
    /* options */
  },
};

Upload an image to see the module in action.

CSS

In order to make the editor match the design of this website (and the whole shadcn/ui theme), I had to add this css file: You should copy this and add it to your project.

@import "@vueup/vue-quill/dist/vue-quill.snow.css";
@import "@vueup/vue-quill/dist/vue-quill.bubble.css";

.ql-toolbar {
  &.ql-snow {
    @apply rounded-t-md border-border font-sans;
  }
  &.ql-snow {
    .ql-stroke {
      @apply stroke-muted-foreground;
    }
    .ql-fill {
      @apply fill-muted-foreground;
    }
    button {
      @apply mx-0.5 rounded hover:bg-muted hover:text-foreground;
      &:hover {
        .ql-fill {
          @apply fill-foreground;
        }
        .ql-stroke {
          @apply stroke-foreground;
        }
      }
      .ql-stroke {
        @apply stroke-muted-foreground;
      }
      &.ql-active {
        @apply bg-primary text-primary-foreground;
        .ql-stroke {
          @apply stroke-primary-foreground;
        }
        .ql-fill {
          @apply fill-primary-foreground;
        }
      }
    }
    .ql-formats {
      svg,
      .ql-picker-label,
      .ql-picker {
        @apply text-muted-foreground;
      }
      button {
        @apply mx-0.5 rounded hover:bg-muted hover:text-foreground;
        &:hover {
          .ql-fill {
            @apply fill-foreground;
          }
          .ql-stroke {
            @apply stroke-foreground;
          }
        }
        .ql-fill {
          @apply fill-muted-foreground;
        }
        .ql-stroke {
          @apply stroke-muted-foreground;
        }
        &.ql-active {
          @apply bg-primary text-primary-foreground;
          .ql-fill {
            @apply fill-primary-foreground;
          }
          .ql-stroke {
            @apply stroke-primary-foreground;
          }
        }
      }
      .ql-picker {
        @apply rounded;
        .ql-picker-options {
          @apply mt-1 rounded border-border bg-card p-1;
          .ql-picker-item {
            @apply rounded hover:bg-muted hover:text-foreground;
            &.ql-selected {
              @apply bg-primary text-primary-foreground;
            }
          }
        }
      }
      .ql-align,
      .ql-color-picker {
        &:hover {
          .ql-fill {
            @apply fill-foreground;
          }
          .ql-stroke {
            @apply stroke-foreground;
          }
        }
        .ql-picker-options {
          @apply mt-1 rounded border-border bg-card p-1;
          .ql-picker-item {
            &.ql-selected {
              @apply bg-primary text-primary-foreground;
              .ql-stroke {
                @apply stroke-primary-foreground;
              }
            }
          }
        }
      }
      .ql-picker-label {
        @apply hover:rounded hover:bg-muted hover:text-foreground;
        &.ql-active {
          @apply rounded bg-primary text-primary-foreground;
          .ql-stroke {
            @apply stroke-primary-foreground;
          }
        }
      }
      .ql-stroke {
        @apply stroke-muted-foreground;
      }
      .ql-expanded {
        .ql-picker-label {
          @apply rounded border-border;
        }
      }
    }
  }
}

.ql-container {
  @apply min-h-[150px] font-sans text-sm;
  &.ql-snow {
    a {
      @apply text-sky-500 hover:text-sky-500;
    }
    @apply rounded-b-md border-border;
  }
  .ql-editor {
    @apply min-h-[150px];
    .ql-font-monospace {
      @apply font-mono;
    }
    &.ql-blank {
      &:before {
        @apply not-italic text-muted-foreground;
      }
    }
  }
  .ql-tooltip {
    @apply z-[9999] rounded border-border bg-card px-4 py-2 text-sm text-card-foreground shadow before:cursor-pointer before:font-medium;
    input[type="text"] {
      @apply h-8 w-[200px] rounded border-border bg-muted/30 p-2 text-sm text-foreground focus:outline-none focus:ring-1 focus:ring-ring;
    }
    .ql-preview {
      @apply text-sm leading-[26px] underline underline-offset-2;
    }
    .ql-remove {
      @apply text-destructive hover:text-destructive;
    }
  }
}

.ql-container.ql-bubble {
  @apply rounded-md border;
  .ql-tooltip {
    @apply z-[9999] rounded-lg border border-border bg-card px-4 py-2 text-sm text-card-foreground shadow before:cursor-pointer before:font-medium;
    input[type="text"] {
      @apply h-8 w-[200px] rounded border-border bg-muted/30 p-2 text-sm text-foreground focus:outline-none focus:ring-1 focus:ring-ring;
    }
    .ql-preview {
      @apply text-sm leading-[26px] underline underline-offset-2;
    }
    .ql-remove {
      @apply text-destructive hover:text-destructive;
    }
    &:not(.ql-flip) .ql-tooltip-arrow {
      @apply border-b-border;
    }
    .ql-toolbar {
      .ql-stroke {
        @apply stroke-muted-foreground;
      }
      .ql-fill {
        @apply fill-muted-foreground;
      }

      button {
        @apply mx-0.5 rounded hover:bg-muted hover:text-foreground;
        &:hover {
          .ql-fill {
            @apply fill-foreground;
          }
          .ql-stroke {
            @apply stroke-foreground;
          }
        }
        .ql-stroke {
          @apply stroke-muted-foreground;
        }
        &.ql-active {
          @apply bg-primary text-primary-foreground;
          .ql-stroke {
            @apply stroke-primary-foreground;
          }
          .ql-fill {
            @apply fill-primary-foreground;
          }
        }
      }
      .ql-formats {
        svg,
        .ql-picker-label,
        .ql-picker {
          @apply text-muted-foreground;
        }
        button {
          @apply mx-0.5 rounded hover:bg-muted hover:text-foreground;
          &:hover {
            .ql-fill {
              @apply fill-foreground;
            }
            .ql-stroke {
              @apply stroke-foreground;
            }
          }
          .ql-fill {
            @apply fill-muted-foreground;
          }
          .ql-stroke {
            @apply stroke-muted-foreground;
          }
          &.ql-active {
            @apply bg-primary text-primary-foreground;
            .ql-fill {
              @apply fill-primary-foreground;
            }
            .ql-stroke {
              @apply stroke-primary-foreground;
            }
          }
        }
        .ql-picker {
          @apply rounded;
          .ql-picker-options {
            @apply mt-1 rounded border border-border bg-card p-1;
            .ql-picker-item {
              @apply rounded px-1 hover:bg-muted hover:text-foreground;
              &.ql-selected {
                @apply bg-primary text-primary-foreground;
              }
            }
          }
        }
        .ql-align,
        .ql-color-picker {
          &:hover {
            .ql-fill {
              @apply fill-foreground;
            }
            .ql-stroke {
              @apply stroke-foreground;
            }
          }
          .ql-picker-options {
            @apply mt-1 rounded border border-border bg-card p-1;
            .ql-picker-item {
              &.ql-selected {
                @apply bg-primary text-primary-foreground;
                .ql-stroke {
                  @apply stroke-primary-foreground;
                }
              }
            }
          }
        }
        .ql-picker-label {
          @apply hover:rounded hover:bg-muted hover:text-foreground;
          &.ql-active {
            @apply rounded bg-primary text-primary-foreground;
            .ql-stroke {
              @apply stroke-primary-foreground;
            }
          }
        }
        .ql-stroke {
          @apply stroke-muted-foreground;
        }
        .ql-expanded {
          .ql-picker-label {
            @apply rounded border-border;
          }
        }
      }
    }
  }
}