---
title: 'Typography and typofilter.js'
url: 'https://graker.ru/en/news/2026/11/12/typography-and-typofilterjs'
language: en
type: post
author: 'Roman Grachev'
published: '2026-11-12T15:50:41+03:00'
updated: '2026-11-13T10:21:41+03:00'
categories:
  - Drupals
  - Programming
  - 'Software Development'
translations:
  ru: 'https://graker.ru/news/2026/11/12/tipografika-i-typofilterjs.md'
---

# Typography and typofilter.js

Long ago I wrote [typofilter.js](https://graker.ru/en/project/typofilter_js): a small script that puts in «guillemets», em dashes, ellipses, © and assorted little arrows. On the previous version of my home page it lived in the Markdown editor of OctoberCMS, I also made it work with old versions of CKE and TinyMCE, and even built a plugin for Obsidian.

These days, of course, fans of typographically correct text are under threat. Any layman who sees em dashes and guillemets instantly recognizes the author as a generative neural network. But what do you do if you were putting in your little dashes and fancy quotes long before the first neural network recognized a kitten in a picture? What if you've grown attached to typography? Or, the other way around, you want to quietly blend in with the robots and become indistinguishable from them, so nobody accidentally replaces you?

Anyway, personally I'm not giving up typography. So I dragged the plugin over to the new version of the site and got it to play nice with Drupal and CKEditor 5. Fixed a few little issues along the way, too.

#### What we fixed in typofilter.js

The library is used in other places, not only here. So first we pinned down its current behavior: Claude and I collected about fifty examples (plain text, Markdown, HTML), recorded what it outputs, and turned that into tests on the built-in `node:test`. After that, every change was checked against this set: only what we're fixing is supposed to change. Plus we added an idempotency check: running it again over already processed text changes nothing, so you can hit the button as many times as you like.

Along the way we fixed several bugs related to Markdown, nested quotes and blocks protected (from typography).

#### The CKEditor 5 plugin

The CKEditor 4 plugin had to be thrown out entirely: the API is different, and the logic was outdated too (it didn't even support tags yet).

The new plugin adds a toolbar button, the `typofilter` command and the Ctrl+Shift+T hotkey (Cmd+Shift+T on a Mac). If something is selected, the selection is processed; if not, the whole text.

The plugin is plain JS with no build step. The main subtlety was where to get the CKEditor classes from: in Drupal the editor is built as a DLL (`CKEditor5.dll(…)`), in the browser build from the CDN everything sits in the global `CKEDITOR`, and when building with npm the classes are imported from the `ckeditor5` package. So the plugin is created by a factory from the classes of whatever editor is on the page, and it finds the DLL or UMD build by itself.

We tested all three options on the current CKEditor 5 v49 and on the version that ships with Drupal 11.

#### How to install it on a site with CKEditor 5

The simplest option is the browser (UMD) build of CKEditor. Load `typofilter.js` and the plugin after it, then add `Typofilter` to the plugins and `typofilter` to the toolbar in the config:

```javascript
<link rel="stylesheet" href="/ckeditor5/ckeditor5.css">
<script src="/ckeditor5/ckeditor5.umd.js"></script>
<script src="/typofilter/typofilter.js"></script>
<script src="/typofilter/plugins/ckeditor5/typofilter.js"></script>
<script>
  const { ClassicEditor, Essentials, Paragraph, Bold, Link } = CKEDITOR;

  ClassicEditor.create(document.querySelector('#editor'), {
    licenseKey: 'GPL',
    plugins: [Essentials, Paragraph, Bold, Link, Typofilter],
    toolbar: ['bold', 'link', '|', 'typofilter'],
    typofilter: { label: 'Typography', keystroke: 'Ctrl+Shift+T' },
  });
</script>
```

The CKEditor files come from the `ckeditor5` npm package (`dist/browser/`) or from their CDN. `licenseKey: 'GPL'` is required starting with CKEditor 44; without it the editor won't start.

If the editor is built with npm and a bundler, you can install the library straight from GitHub (`npm install github:graker/typofilter.js`) and build the plugin from the classes of the `ckeditor5` package:

```javascript
import { ClassicEditor, Essentials, Paragraph, Plugin, Command, ButtonView } from 'ckeditor5';
import 'typofilter.js/typofilter.js';
import 'typofilter.js/plugins/ckeditor5/typofilter.js';

const Typofilter = window.createTypofilterPlugin({ Plugin, Command, ButtonView });

ClassicEditor.create(document.querySelector('#editor'), {
  licenseKey: 'GPL',
  plugins: [Essentials, Paragraph, Typofilter],
  toolbar: ['typofilter'],
});
```

#### How to install it on Drupal

The library now has a `composer.json` with the `drupal-library` type, so in a standard Drupal project (`drupal/recommended-project`) composer will put it into `web/libraries/typofilter`. There's no package on Packagist, so we add the repository:

```bash
composer config repositories.typofilter vcs https://github.com/graker/typofilter.js
composer require graker/typofilter:^1.2
```

Drupal looks for CKEditor 5 plugins only in modules, so you'll need a module of your own (or any existing custom one). Let's call it `mymodule`. First we describe the libraries in `mymodule.libraries.yml`:

```yaml
typofilter:
  js:
    /libraries/typofilter/typofilter.js: {}

ckeditor5.typofilter:
  js:
    /libraries/typofilter/plugins/ckeditor5/typofilter.js: {}
  dependencies:
    - core/ckeditor5
    - mymodule/typofilter
```

Better not to set `version` for the library: Drupal will put it into the file URL (`?v=1.2.0`), and after the library is updated, browsers will keep the old file until you bump the version by hand. Without it, the URL changes on every cache rebuild.

Next we register the plugin in `mymodule.ckeditor5.yml`:

```yaml
mymodule_typofilter:
  ckeditor5:
    plugins:
      - typofilter.Typofilter
  drupal:
    label: Typography
    library: mymodule/ckeditor5.typofilter
    toolbar_items:
      typofilter:
        label: Typography
    elements: false
```

Rebuild the cache (`drush cr`), go to "Configuration → Content authoring → Text formats and editors", open the formats you need and drag the "Typography" button onto the toolbar.

A couple of optional details. To give the button an icon on the toolbar configuration screen instead of an empty square, you need an admin library with CSS for the `.ckeditor5-toolbar-button-typofilter` class, and the SVG is in the library: `plugins/ckeditor5/typofilter.svg`. And to make the button label translatable, you can set your own plugin class in `drupal.class` (a subclass of `CKEditor5PluginDefault`) and return a config like `['typofilter' => ['label' => (string) $this->t('Typography')]]` from `getDynamicPluginConfig()`.

Code and tests are [on GitHub](https://github.com/graker/typofilter.js), help yourself. Drop me a line if you have any questions.
