Search by

PHP and Laravel integration for the Lilac WYSIWYG editor — render the editor from server-side PHP or a Blade component.

Package info

github.com/maifeeulasad/lilac

Language:TypeScript

pkg:composer/lilac-wysiwyg/lilac

Statistics

Installs: 2

Dependents: 0

Suggesters: 0

Stars: 14

Open Issues: 0

v0.7.1 2026-09-22 13:48 UTC

This package is auto-updated.

Last update: 2026-09-22 13:49:12 UTC


README

A smooth, modern WYSIWYG text editor built with TypeScript. Framework-agnostic core with adapters for React, Preact, Vue, Svelte, Solid, Angular, Lit, Qwik, Alpine.js, Ember, Astro, and Vanilla JS.

TypeScript Zero Dependencies Framework Agnostic

Architecture

Lilac follows an adapter pattern for maximum framework compatibility. The core functionality is isolated from framework-specific implementations, ensuring consistent behavior across all platforms.

lilac/
├── core/                    # Framework-agnostic core
│   ├── types/               # TypeScript type definitions
│   ├── plugins/              # Plugin system (emoji, table, word count)
│   ├── utils/                # Utility functions (formatting, icons)
│   ├── components/           # Core components (Editor, Toolbar)
│   └── index.ts              # Core exports
├── adapter/                  # Framework-specific adapters
│   ├── react/                # React component wrapper
│   ├── preact/               # Preact component wrapper
│   ├── vue/                  # Vue component
│   ├── svelte/               # Svelte action
│   ├── solid/                # SolidJS component + use:lilac directive
│   ├── angular/              # Angular component/directive
│   ├── lit/                  # <lilac-editor> Web Component
│   ├── qwik/                 # Qwik component
│   ├── alpine/               # Alpine.js x-lilac directive
│   ├── ember/                # Ember {{lilac}} modifier
│   ├── astro/                # Astro mount helper + integration
│   └── vanilla/              # Vanilla JS wrapper
└── docs/                     # Documentation & demos

Core Design Principles

  1. UI Instructions in Core: The core package contains all UI styling instructions to ensure consistent appearance across all framework adapters
  2. Framework-Agnostic Logic: Business logic and state management are kept framework-independent
  3. Adapter Pattern: Each framework gets its own adapter that bridges the core with framework-specific patterns

Features

  • Framework-Agnostic Core: Pure TypeScript implementation with no dependencies
  • Consistent UI: Centralized UI instructions ensure identical styling across all adapters
  • Plugin System: Extensible architecture with built-in plugins for emojis, tables, and word count
  • Rich Text Formatting: Bold, italic, underline, strikethrough, headings, lists, blockquotes, code blocks
  • Media Support: Link and image insertion with keyboard shortcuts
  • Keyboard Shortcuts: Ctrl/Cmd + B for bold, Ctrl/Cmd + I for italic, etc.
  • Undo/Redo: Full history support with 50-step undo stack
  • Theme Support: Light and dark themes with CSS custom properties
  • Accessibility: ARIA labels on the toolbar buttons and editable region, plus full keyboard navigation
  • Mobile-friendly: 44px touch targets on coarse pointers, a scrollable toolbar, and a 16px editable font so iOS doesn't zoom on focus
  • Plugin API: Create custom plugins with toolbar buttons, keyboard shortcuts, and lifecycle hooks

Installation

Core (Vanilla JS / TypeScript)

npm install @lilac-wysiwyg/core
pnpm add @lilac-wysiwyg/core
yarn add @lilac-wysiwyg/core

Framework Adapters

Every adapter is a thin wrapper over the same core, published under the @lilac-wysiwyg/* scope and built with plain tsc. Install one with your package manager of choice (npm install, pnpm add, or yarn add).

Framework Package Supported versions Integration Docs
React @lilac-wysiwyg/react React ≥ 16.8 (hooks) <LilacEditor> component + imperative ref react.html
Preact @lilac-wysiwyg/preact Preact ≥ 10 <LilacEditor> component + imperative ref preact.html
Vue @lilac-wysiwyg/vue Vue ≥ 3 <LilacEditor> component with v-model vue.html
Svelte @lilac-wysiwyg/svelte Svelte ≥ 4 (works in 4 & 5) use:lilac action svelte.html
Solid @lilac-wysiwyg/solid solid-js ≥ 1.6 <LilacEditor> component + use:lilac directive solid.html
Angular @lilac-wysiwyg/angular @angular/core ≥ 14 Component + directive (module) angular.html
Lit @lilac-wysiwyg/lit lit ≥ 2 <lilac-editor> custom element lit.html
Qwik @lilac-wysiwyg/qwik @builder.io/qwik ≥ 1.5 <LilacEditor> component$ (QRL handlers) qwik.html
Alpine.js @lilac-wysiwyg/alpine alpinejs ≥ 3 x-lilac directive (plugin) alpine.html
Ember @lilac-wysiwyg/ember ember-source ≥ 4, ember-modifier ≥ 4 {{lilac}} element modifier ember.html
Astro @lilac-wysiwyg/astro astro ≥ 3 (optional peer) mountLilacEditor() + lilac() integration astro.html
Vanilla JS @lilac-wysiwyg/vanilla — (no framework) new LilacEditor() / factory vanilla.html

All version ranges are declared as peer dependencies, so the adapter uses the copy of the framework already in your app. React also peers on react-dom (≥ 16.8).

Script tag (UMD / CDN)

For no-bundler pages, load the UMD build and use the Lilac global:

<script src="https://cdn.jsdelivr.net/npm/@lilac-wysiwyg/core/dist/lilac.umd.js"></script>
<div id="editor"></div>
<script>
  new Lilac.LilacEditor({ container: document.getElementById('editor'), toolbar: { show: true } });
</script>

PHP & Laravel

A Composer package renders the editor server-side (it emits the container + the UMD loader script). See the PHP docs.

composer require lilac-wysiwyg/lilac
use Lilac\Editor;

echo Editor::render(['toolbar' => ['show' => true], 'placeholder' => 'Write…']);

In Laravel the service provider is auto-discovered, so a Blade view can use the component directly:

<x-lilac :value="old('body', $post->body)" placeholder="Write…" />

Quick Start

Vanilla JS / TypeScript (Core)

import { LilacEditor } from '@lilac-wysiwyg/core';

// Styles are injected automatically on construction. To supply your own
// stylesheet instead, pass `injectStyles: false` below.

// Create editor instance
const editor = new LilacEditor({
  container: document.getElementById('editor')!,
  toolbar: { show: true },
  placeholder: 'Start writing...',
  onChange: (content) => {
    console.log('Content:', content);
  }
});

// Get content
const content = editor.getContent();

// Set content
editor.setContent('<p>Hello World!</p>');

React

import { useState } from 'react';
import { LilacEditor } from '@lilac-wysiwyg/react';

function App() {
  const [content, setContent] = useState('<p>Hello!</p>');

  return (
    <LilacEditor
      value={content}
      onChange={setContent}
      toolbar={{ show: true }}
      placeholder="Start writing..."
    />
  );
}

Vue 3

<template>
  <LilacEditor
    v-model="content"
    :toolbar="{ show: true }"
    placeholder="Start writing..."
  />
</template>

<script setup>
import { ref } from 'vue';
import { LilacEditor } from '@lilac-wysiwyg/vue';

const content = ref('<p>Hello!</p>');
</script>

Svelte

The Svelte adapter ships an action, not a component, so it works on both Svelte 4 and 5 without a compiler step:

<script>
  import { lilac } from '@lilac-wysiwyg/svelte';

  let content = '<p>Hello!</p>';
</script>

<div use:lilac={{ value: content, onChange: (c) => (content = c), toolbar: true }} />

Solid

The Solid adapter offers both a component and a use:lilac directive:

import { createSignal } from 'solid-js';
import { LilacEditor } from '@lilac-wysiwyg/solid';

function App() {
  const [content, setContent] = createSignal('<p>Hello!</p>');

  return <LilacEditor value={content()} onChange={setContent} toolbar />;
}

Lit / Web Components

Importing the package registers a <lilac-editor> custom element, usable in plain HTML or any framework:

<script type="module">
  import '@lilac-wysiwyg/lit';
</script>

<lilac-editor value="<p>Hello!</p>" toolbar></lilac-editor>
<script>
  document
    .querySelector('lilac-editor')
    .addEventListener('change', (e) => console.log(e.detail));
</script>

Using Built-in Plugins

Pass plugins through the plugins option — the editor installs them into its own manager on construction:

import {
  LilacEditor,
  wordCountPlugin,
  emojiPlugin,
  tablePlugin
} from '@lilac-wysiwyg/core';

const editor = new LilacEditor({
  container: document.getElementById('editor')!,
  toolbar: { show: true },
  plugins: [wordCountPlugin, emojiPlugin, tablePlugin],
  onChange: (content) => console.log('Content:', content)
});

The exported pluginManager singleton is a standalone registry; installing into it does not affect an editor instance. Use the plugins option above.

API Reference

LilacEditor Constructor Options

Option Type Default Description
container HTMLElement required DOM element to mount the editor
initialContent string '' Initial HTML content of the editor
placeholder string 'Start writing...' Placeholder text when editor is empty
readOnly boolean false Whether the editor is read-only
autoFocus boolean false Auto-focus editor on mount
theme 'light' | 'dark' | 'auto' 'light' Editor theme ('auto' follows the OS preference)
sanitize boolean true Sanitize HTML from initialContent/setContent (stored-XSS guard)
injectStyles boolean true Inject Lilac's stylesheet into document.head on construction
minHeight number undefined Minimum editor height in pixels
maxHeight number undefined Maximum editor height in pixels
maxLength number undefined Maximum content length
className string undefined Extra class name on the editor root
onChange (content: string) => void undefined Content change callback
onSelectionChange (selection: SelectionRange | null) => void undefined Selection change callback
onFocus () => void undefined Focus event callback
onBlur () => void undefined Blur event callback
toolbar ToolbarConfig undefined Toolbar configuration
plugins EditorPlugin[] [] Plugins to install

LilacEditor Methods

Method Description
getContent() Get current editor HTML content
setContent(content) Set editor content
focus() Focus the editor
blur() Blur the editor
undo() Undo last change
redo() Redo last undone change
setReadOnly(readOnly) Enable/disable read-only mode
destroy() Clean up and remove the editor

Toolbar Tools

Tool Keyboard Shortcut
Bold Ctrl/Cmd + B
Italic Ctrl/Cmd + I
Underline Ctrl/Cmd + U
Strikethrough -
Heading 1-3 -
Paragraph -
Bullet List -
Ordered List -
Blockquote -
Code Block -
Link Ctrl/Cmd + K
Image -

Plugin System

Lilac features a powerful plugin system that allows extending the editor with custom functionality.

Built-in Plugins

  • Word Count Plugin: Real-time document statistics in a side panel, toggled from a toolbar button
  • Emoji Picker Plugin: Insert emojis with an easy-to-use picker (Ctrl+Shift+E)
  • Table Inserter Plugin: Insert and manage HTML tables (Ctrl+Shift+T)

Creating Custom Plugins

import type { EditorPlugin } from '@lilac-wysiwyg/core';

export const myCustomPlugin: EditorPlugin = {
  id: 'my-custom-plugin',
  name: 'My Custom Plugin',
  version: '1.0.0',

  // Toolbar buttons
  toolbarButtons: [{
    id: 'my-button',
    icon: '<svg>...</svg>',
    label: 'My Tool',
    tooltip: 'My custom tool',
    onClick: (context) => {
      context.insertContent('<strong>Custom!</strong>');
    },
  }],

  // Keyboard shortcuts
  keyboardShortcuts: [{
    key: 'm',
    ctrlKey: true,
    action: (context) => {
      context.insertContent('<em>Shortcut!</em>');
    },
  }],

  // Lifecycle hooks
  onInstall: (context) => console.log('Installed'),
  onEditorMount: (context) => console.log('Ready'),
  onContentChange: (content, context) => {
    // React to content changes
  },
};

Images

Drop an image file onto the editor, or paste one from the clipboard, and it is embedded at the caret. By default the image is inlined as a base64 data: URL — fully client-side, no server required:

const editor = new LilacEditor({
  container,
  toolbar: { show: true },
  maxImageSize: 5 * 1024 * 1024, // optional: reject images over 5 MB
});

To upload to your own storage instead of inlining, provide onImageUpload and return the URL to embed:

const editor = new LilacEditor({
  container,
  toolbar: { show: true },
  onImageUpload: async (file) => {
    const url = await myUploader(file); // your storage
    return url;
  },
});

Non-image drops and pastes fall through to the editor's normal handling.

Markdown Export / Import

Round-trip editor content to and from Markdown. The conversion is hand-written and zero-dependency, covering the subset the editor produces (headings, bold/italic/strikethrough, inline code and code blocks, links, images, blockquotes, lists, rules, paragraphs):

const md = editor.getMarkdown();      // serialize current content to Markdown
editor.setMarkdown('# Hello\n\nWorld'); // replace content from Markdown

The converters are also exported directly:

import { toMarkdown, fromMarkdown } from '@lilac-wysiwyg/core';

toMarkdown('<h1>Hi</h1><p>a <strong>b</strong></p>'); // "# Hi\n\na **b**"
fromMarkdown('- one\n- two');                          // "<ul><li>one</li><li>two</li></ul>"

Constructs outside that subset (tables, nested lists, HTML passthrough, underline — which has no Markdown equivalent) are not converted.

Find & Replace

Press Ctrl/Cmd + F inside the editor to open the find bar — match count, next/previous, replace, replace-all, and case-sensitive / whole-word toggles. Esc closes it, and because the shortcut is bound to the editable region, the browser's own find still works when the editor isn't focused.

editor.openFind();  // open the panel programmatically
editor.closeFind(); // close it

The matching engine is also exported for non-DOM use (e.g. searching content before it is mounted):

import { findMatches, replaceAll } from '@lilac-wysiwyg/core';

findMatches('the cat sat', 'at');                 // [{ start: 5, end: 7 }, { start: 9, end: 11 }]
replaceAll('cat cat', 'cat', 'dog', { wholeWord: true }); // { text: 'dog dog', count: 2 }

Customization

Themes

Override CSS custom properties to create custom themes:

.lilac-editor {
  --lilac-color-primary: #your-color;
  --lilac-color-background: #your-bg;
  --lilac-border-radius: 8px;
}

Development

# Install dependencies
pnpm install

# Build core library
pnpm build

# Watch for changes
pnpm dev

# Type check
pnpm typecheck

Documentation

Visit our GitHub Pages for per-framework documentation, with a live interactive demo on the Vanilla JS page.

Roadmap

  • Rich text toolbar with all formatting options
  • Plugin system with built-in plugins
  • Emoji picker
  • Table inserter
  • Word count plugin
  • React adapter
  • Preact adapter
  • Svelte adapter
  • Solid adapter
  • Angular adapter
  • Vue adapter
  • Lit adapter
  • Qwik adapter
  • Alpine.js adapter
  • Ember adapter
  • Astro adapter
  • Vanilla JS adapter
  • UMD / CDN (script-tag) build
  • PHP & Laravel integration
  • Markdown export/import
  • Image upload and embedding
  • Find and replace
  • Mobile optimizations

Contributing

We welcome contributions! Please see our Contributing Guide for details.

License

MIT License - See LICENSE for details.

Author

Maifee Ul Asad maifeeulasad@gmail.com

Preview

preview of lilac

Made with by maifeeulasad

If you find this project useful, please star us on GitHub!