0033. Editor icons are BlockNote's own, held as documented icon assets¶
Status: Accepted
Date: 2026-09-26
Context¶
Several of the editor's own buttons draw a hand-drawn icon rather than upstream's own glyph: js/src/links.tsx's OpenLinkIcon and LinkIcon, js/src/entity-link-toolbar.tsx's UnlinkIcon, and js/src/toolbar.tsx's SubscriptIcon and SuperscriptIcon, the last two drawn from SVG <text> and so dependent on whatever font the browser picks for it. js/src/menus.tsx's AddBlockIcon is not hand-drawn: it is react-icons' AiOutlinePlus path data, from the Ant Design icon set, copied in without the MIT notice that set carries. modules/blocknote_media/js/icon.tsx draws ReplaceIcon, PreviewIcon, AlignLeftIcon, AlignCenterIcon, AlignRightIcon and CaptionIcon the same hand-drawn way, and its MediaIcon is core's own default_admin media navigation drawing, copied in under GPL-2.0-or-later. The link form's two text inputs pass icon={null} and show none. tests/modules/blocknote_test/js/ draws no icon of its own.
Upstream draws every one of these functions already, from react-icons/ri (Remix Icon) for most of them and react-icons/ai (Ant Design) for Add block: RiLink for the create-link button and the URL field, RiText for the text field, RiExternalLinkFill for Open, RiLinkUnlink for Unlink, RiImageEditFill for file replace, RiImageAddFill for the file preview toggle, RiAlignLeft/RiAlignCenter/RiAlignRight for text alignment, RiInputField for the file caption toggle, and AiOutlinePlus for Add block. react-icons is a devDependency of @blocknote/react and @blocknote/mantine; it is not installed in this repository and is inlined into their published builds as minified, name-stripped functions, so it cannot be imported as a package, and the source of a glyph upstream draws is the icon set's own repository rather than anything resolvable from node_modules here. Amended 2026-09-26. Not every one of these functions: upstream has no subscript or superscript button, as the Decision below says, and no slash-menu item for media of mixed kinds, which is why the media item's icon is the one exception.
react-icons 5.6.0 pins Remix Icon to commit 8bc436bdd329cf342958d8effdd078bfa74354c6, confirmed against the tag with gh api repos/Remix-Design/RemixIcon/git/ref/tags/v4.6.0, which resolves to exactly that commit: the v4.6.0 tag. At that commit License is the stock Apache License 2.0 text, unmodified, and the repository ships no NOTICE file; verified directly, https://raw.githubusercontent.com/Remix-Design/RemixIcon/v4.6.0/License returns the standard Apache-2.0 license and https://raw.githubusercontent.com/Remix-Design/RemixIcon/v4.6.0/NOTICE returns 404. Every glyph needed here exists at that commit as icons/<Category>/<name>.svg, one <path> each, viewBox="0 0 24 24" fill="currentColor". Remix changed its own license to "Remix Icon License v1.0" in January 2026, first shipped in Remix Icon v4.9.0; react-icons 5.7.0 and later pin a commit under that later, non-free license while still labeling the package Apache-2.0 in its own metadata, so a react-icons version alone is not proof of what license the pinned Remix commit actually carries. Amended 2026-10-31. BlockNote 0.57.0 still builds react-icons 5.6.0 in: every react-icons module its published @blocknote/react and @blocknote/mantine files inline is commented with a path under react-icons@5.6.0, and the release's own pnpm-lock.yaml resolves react-icons to 5.6.0 alone.
drupal.org's git repository usage policy (node 1001544) and its policy on third-party assets (node 422996) permit a GPL-incompatible non-code asset, icons named explicitly, to be distributed "in aggregate" with GPL code under a free license, provided its provenance, license and source are documented in a file in the repository; the compatibility allowlist (node 1475972) marks Apache-2.0 "not allowlisted" for code, with the note that the question is still under discussion. Neither page distinguishes a documented .svg asset file from the same glyph pasted as path data inside a .tsx component the way AddBlockIcon and the hand-drawn icons above are; the module's own committed bundle already carries 55 Remix and 4 Material Design (both Apache-2.0) glyphs and one CC BY 4.0 glyph (FaCode) through upstream's inlining, none of them named in THIRD_PARTY_NOTICES.md today. docs/drafts/lwg-icon-assets-question.md asks the Licensing Working Group whether a documented .svg asset directory and its inlining meet the non-code-asset rule; disclosing the undocumented upstream-inlined glyphs is left to a later wave (section 9 of the plan this record implements), because listing and licensing all of them is a larger piece of work than icons this module draws itself. Amended 2026-09-26. The plan this paragraph and Consequences cite as "section 9" is not part of this repository. The later work it names is listing in THIRD_PARTY_NOTICES.md the icon sets upstream's packages inline through react-icons, with their licenses and glyph counts, deciding about the one CC BY 4.0 glyph, deciding whether Apache-2.0 stays in the generator's ALLOWED set, and giving blocknote.libraries.yml a license: key. docs/drafts/lwg-icon-assets-question.md does disclose the upstream-inlined glyphs to the working group; what is left for later is naming them in the notices. Amended 2026-10-30. The question is filed with the Licensing Working Group as #3578951, after 1.0.x was pushed with both text.svg files, and its companion, the request about the MPL-2.0 packages in the bundle, as #3578950. Both are awaiting an answer, so the working group has not ruled on either, and the hand-redrawn fallback below stays a fallback. Amended 2026-10-31. On BlockNote 0.57.0 the bundle carries 51 Remix glyphs, 4 Material Design, 1 each from Ant Design, Tabler and Heroicons, and the same Font Awesome glyph, counted by matching each glyph's path data in the published files against dist/blocknote.js; the Octicons and Lucide glyphs 0.57.0 adds draw only the version history sidebar, @blocknote/react/versioning, which the bundle does not import.
Nodar decided, 2026-09-26, against the planner's recommendation, that the media block's slash-menu item, empty state and name line keep core's own media icon rather than move to a Remix glyph: it is the drawing an author already recognizes from Drupal's administration navigation, and nothing in Remix Icon names a mixed-kind "Media" the way core's own icon does.
Decision¶
Priority order: upstream's own choice for the function, then Remix Icon, then something else. Every icon this module draws for a function upstream also draws is the same glyph upstream draws it with, read off Remix Icon rather than off react-icons, because react-icons cannot be imported. Subscript and superscript, which upstream has no button for, take Remix's own subscript and superscript glyphs (the "X" form, not the "T" form subscript-2/superscript-2). Add block keeps Ant Design's outlined/plus path data, because that source is already MIT and already lives as code; nothing is gained by moving it into an asset file. The media item, per the decision above, keeps core's own icon rather than any Remix glyph.
A Remix glyph is held as an unmodified .svg asset file, not as path data inside a component. Each bundle that uses one gets its own icons/remix/ directory: js/icons/remix/ for the core bundle and modules/blocknote_media/js/icons/remix/ for the media bundle, disjoint sets, because the two bundles' icon needs do not overlap and the media bundle's preset build must not import from js/src. Each directory holds the .svg files fetched verbatim from https://raw.githubusercontent.com/Remix-Design/RemixIcon/v4.6.0/icons/<Category>/<name>.svg, Remix's own License from the same tag as LICENSE, and a README.md naming the source URL, the tag, the commit, the license, that the files are unmodified, and the file list. js/icons/remix/ holds six files: link.svg, text.svg, external-link-fill.svg, link-unlink.svg, subscript.svg, superscript.svg. modules/blocknote_media/js/icons/remix/ holds six files too, not seven: image-edit-fill.svg, image-add-fill.svg, input-field.svg, align-left.svg, align-center.svg, align-right.svg; multi-image-line.svg, which the plan's own inventory would otherwise have added for the media item, is not fetched, because that item keeps core's icon instead. Amended 2026-10-30. modules/blocknote_media/js/icons/remix/ now holds seven files: text.svg was added for the media block's Convert to paragraph control (0019, amended the same day). It is the same Remix glyph and the same bytes as the core bundle's text.svg, copied rather than shared because the media bundle's preset build must not import from js/src. The two sets are therefore no longer disjoint: they overlap in that one file, and the next paragraph's "the media bundle's six" now reads seven. Amended 2026-10-31. It now holds eight: font-family.svg was added for the media block's alternative text override (0036), upstream's own icon for the file rename button, fetched the same way from the same tag; the next paragraph's count now reads eight.
Each Remix asset is inlined at build time with Vite's ?raw import and rendered through dangerouslySetInnerHTML. js/src/icons.tsx exports one component per core-bundle icon, and modules/blocknote_media/js/icon.tsx gains the same shape for the media bundle's six; each renders <span className="blocknote-icon" aria-hidden="true" dangerouslySetInnerHTML={{ __html: raw }} /> from a raw string imported with ?raw, sized at 1em through .blocknote-icon { width: 1em; height: 1em } in links.css so it fills whatever box the kit's button or input draws around it. The docblock on each file says the injected markup is trusted repository content, copied from Remix Icon and never touched by anything an author types. js/src/vite-env.d.ts (core bundle) and modules/blocknote_media/js/vite-env.d.ts (media bundle) each add /// <reference types="vite/client" /> so tsc resolves the ?raw import's type; tsconfig.preset.json, which type-checks the media bundle's sources under types: [], and the main tsconfig.json, which type-checks both, both pick it up as an ambient declaration. Amended 2026-09-26. .blocknote-icon is declared in js/src/icons.css, not links.css as this paragraph said above; and node 1475972, cited in Context and in References, is titled "Drupal.org distribution packaging requirements", not "the compatibility allowlist" as Context calls it, though it is the page that holds the allowlist this record relies on.
MIT path data may stay as code, with its notice. AddBlockIcon moves from js/src/menus.tsx into js/src/icons.tsx unchanged in its drawing, and gains the MIT notice its source carries: "Copyright (c) 2018-present Ant UED, https://xtech.antfin.com/", read directly from ant-design-icons' own LICENSE at commit 375bc01003dfda1eda39aa8742be85a5c0249db8, confirmed against the SPDX id declared in that package's package.json. It does not move into an icons/ directory, because it was already code before this record and nothing about it changed shape.
No runtime member. Neither bundle's icon set is added to Drupal.blocknote. The two bundles' icon needs are disjoint, and the preset's seven externals are a versioned contract this record does not extend; an extension built through the preset copies and documents its own icons the same way, one sentence docs/extending.md gains. Amended 2026-10-30. The needs now overlap in text.svg; one shared glyph is not worth a runtime member either, and the media bundle keeps its own copy, as an extension would.
The notices generator gains a "Copied icon assets" section. scripts/generate-third-party-notices.ts gains a COPIED_ASSETS list, one entry per icons/remix/ directory, each naming the directory, the source repository, the tag, the commit and the license; the generator fails the build when a listed directory or its LICENSE file is missing, the same posture checkCopiedSource() already takes for js/src/mantine.css. THIRD_PARTY_NOTICES.md is regenerated with a "Copied icon assets" section naming both directories. Amended 2026-09-26. Each directory also holds sha256sums.txt, the sha256 of every .svg file and of LICENSE in shasum -a 256 output, and the generator's checkCopiedAssets() recomputes each hash and refuses to generate the notices when a file no longer matches, when a listed file is missing, or when an .svg file in the directory is not listed, so "unmodified" is checked on every bun run notices rather than only stated in the directory's README.md.
Alternatives considered¶
Path data pasted into a .tsx component, the way the hand-drawn icons and AddBlockIcon were before this record. Rejected for a Remix glyph. It is code-shaped rather than asset-shaped, and drupal.org's stated allowance is for icons as non-code assets; a .tsx literal is the container the policy's own allowlist ruling has never been asked about, where a .svg file in an icons/ directory is the shape the policy names in so many words.
react-icons as an installed dependency. Rejected. It publishes no source form that separates one glyph from the rest of a several-thousand-icon package the way this module needs, and adding it as a dependency would pull its own build tooling and every icon set it bundles into the dependency graph for six glyphs.
An icons member on Drupal.blocknote. Rejected, on 0003's own reasoning for the URL field: a runtime member is a versioned contract, and nothing outside the two bundles needs one yet. Amended 2026-09-26. The reasoning for the URL field is 0010's, which rejected registering that field on Drupal.blocknote, not 0003's.
MIT lookalikes (Tabler, Heroicons) in place of the Remix glyphs. Rejected. Tabler is stroked rather than filled and would read as a different visual language beside Mantine's own filled icons; Heroicons was not measured against Mantine's icon proportions. Both were available under a license this repository already accepts without a licensing question, which was not enough to outweigh matching upstream's own choice of glyph.
Redrawing the Remix glyphs by hand, in Remix's style. Rejected as the primary route, kept as the documented fallback if the Licensing Working Group holds that Apache-2.0 material may not sit in this repository at all, even as a documented asset. A hand-redrawn glyph carries no upstream provenance to document and needs no license file, at the cost of being this module's own approximation rather than the glyph an author may already recognize from another Remix-drawn interface.
Moving the media item's icon to Remix's multi-image-line, the planner's own recommendation. Rejected by Nodar, 2026-09-26. Core's own drawing is the one an author already meets in Drupal's administration navigation when reaching for Media, and no Remix glyph names a mixed-kind media item the way core's own icon does; multi-image-line was the nearest candidate, not an exact match.
Consequences¶
The repository now holds two directories of Apache-2.0 material as documented, unmodified asset files, disclosed in THIRD_PARTY_NOTICES.md and each directory's own README.md. This is the module's own, small-footprint answer to the licensing question docs/drafts/lwg-icon-assets-question.md puts to the Licensing Working Group; it does not resolve what to do about the 55 Remix, 4 Material Design and 1 CC BY 4.0 glyph the committed bundle already carries through upstream's own inlining, undocumented, which section 9 of the plan this record implements leaves for a later wave.
A BlockNote upgrade that moves the pinned react-icons version to 5.7.0 or later would, if this module were to read its Remix commit again from that newer pin, bring glyphs under the non-free Remix Icon License v1.0 into the directories this record adds; nothing here re-reads react-icons' pin automatically, so the risk is that a future maintainer updates js/icons/remix/ or modules/blocknote_media/js/icons/remix/ from a later react-icons version's commit without checking what license that commit is actually under. The release checklist has to say so. Amended 2026-09-26. No release checklist in this repository says so. Each icons/remix/README.md now names the relicensing and says that no file there is refreshed from v4.9.0 or any later commit, and a file refreshed anyway fails checkCopiedAssets() until its line in sha256sums.txt is changed too, with a refusal naming the tag and commit the file has to match. Amended 2026-11-01. The Releasing section of CONTRIBUTING.md now carries the warning.
scripts/generate-third-party-notices.ts's ALLOWED set already lists Apache-2.0 for the packages that reach the compiled bundle; this record does not change that set, and does not add a license: key to blocknote.libraries.yml. Both are named in section 9 of the plan as work for a later wave, alongside listing what upstream already inlines. Amended 2026-11-01. That later wave is done. Apache-2.0 is out of the generator's ALLOWED set, so an Apache-2.0 package reaching the bundle as code stops the build and becomes a licensing question first; the Apache-2.0 glyphs are assets, recorded in COPIED_ASSETS and in a new INLINED_ICON_SETS list. THIRD_PARTY_NOTICES.md gains an "Icon glyphs inlined by BlockNote" section that names the six sets upstream's packages inline into dist/blocknote.js through react-icons 5.6.0, with the glyph count of each read from the built bundle on every run, each set's commit and license text, the attribution that the one CC BY 4.0 glyph (FaCode) asks for, and the MIT license of react-icons' own wrapper; the generator refuses a set it does not list. The upstream component that draws FaCode stays. blocknote.libraries.yml and modules/blocknote_media/blocknote_media.libraries.yml each declare one license: entry, GPL-2.0-or-later with bundled MPL-2.0 and MIT code, gpl-compatible: true, with a url to THIRD_PARTY_NOTICES.md on git.drupalcode.org. Because the glyph counts come from the built bundle, bun run verify writes the notices after the build.
dangerouslySetInnerHTML and Vite's ?raw import are new patterns in this repository. bun run check, bun run lint, bun run extension:oot and the committed bundles all have to keep accepting them; this record is the place a future reviewer who has not seen either pattern here before is pointed to.
References¶
- drupal.org git repository usage policy: https://www.drupal.org/node/1001544
- drupal.org policy on third-party assets: https://www.drupal.org/node/422996
- drupal.org GPLv2-compatible licenses table: https://www.drupal.org/node/1475972
- Remix Icon, tag
v4.6.0, commit8bc436bdd329cf342958d8effdd078bfa74354c6: https://github.com/Remix-Design/RemixIcon/tree/v4.6.0 - react-icons 5.6.0,
packages/react-icons/src/icons/index.ts, the file that pins Remix Icon to that commit. - Ant Design icons, commit
375bc01003dfda1eda39aa8742be85a5c0249db8,LICENSE: https://github.com/ant-design/ant-design-icons/blob/375bc01003dfda1eda39aa8742be85a5c0249db8/LICENSE js/icons/remix/,modules/blocknote_media/js/icons/remix/,js/src/icons.tsx,modules/blocknote_media/js/icon.tsx,scripts/generate-third-party-notices.ts,THIRD_PARTY_NOTICES.md.docs/drafts/lwg-icon-assets-question.md.- 0003, the module's provenance-and-licensing posture this record follows for a second kind of third-party material.