Search by

nobrainer / silverstripe-icon-picker

Nobrainer

A self-hosted, SVG-based icon picker field for Silverstripe CMS.

Package info

bitbucket.org/nobrainerweb/silverstripe-icon-picker/

Homepage

Issues

Type:silverstripe-vendormodule

pkg:composer/nobrainer/silverstripe-icon-picker

Statistics

Installs: 3

Dependents: 0

Suggesters: 0

v1.0.2 2026-09-21 09:53 UTC

This package is auto-updated.

Last update: 2026-09-21 09:59:55 UTC


README

A self-hosted, SVG-based icon picker field for Silverstripe CMS. Editors pick an icon from a searchable modal; the choice renders as inline <svg> in templates, styleable with plain CSS.

  • Ships the full Tabler icon set (v3.46.0, 6,184 icons, outline and filled) as SVG files, not a font — no paid tier, no external CDN.
  • Swap in a different icon library, or add your own icons, purely through YAML config.
  • Renders real inline SVG, so color, width and hover states work like any other element.

Requirements

  • PHP 8.3+
  • Silverstripe CMS 6 (silverstripe/framework ^6, silverstripe/admin ^3)

Installation

composer require nobrainer/silverstripe-icon-picker

Then run dev/build to generate the icon manifest.

Usage

Declare the field as DBIcon:

use Nobrainer\IconPicker\ORM\FieldType\DBIcon;

private static array $db = [
    'Icon' => DBIcon::class,
];

That's all you need — getCMSFields() scaffolds the picker from $db automatically, on a plain DataObject and on a Page/SiteTree subclass alike.

To control where the field lands (a specific tab, alongside other fields), place it explicitly instead:

use Nobrainer\IconPicker\Forms\IconPickerField;

public function getCMSFields()
{
    $fields = parent::getCMSFields();
    // Scaffolding already added a field named 'Icon' - remove it before adding your own.
    $fields->removeByName('Icon');
    $fields->addFieldToTab('Root.Main', IconPickerField::create('Icon', 'Icon'));

    return $fields;
}

Don't add a : FieldList return type there — core SiteTree subclasses such as ErrorPage declare getCMSFields() without one, and a narrower signature on your Page is a fatal error.

Render it in a template. The field outputs the <svg> directly, with no wrapper element, so it can sit inline in text or inside a button:

<% if $Icon %>$Icon<% end_if %>

Each icon carries a base class and a per-icon modifier — class="icon-picker-icon icon-picker-icon--home" — so you can style all icons at once or target one:

.icon-picker-icon {
    width: 24px;
    height: 24px;
    color: currentColor;
}

Configuration

Icon sources

A source is a folder of SVGs plus a label. The module ships with two:

Nobrainer\IconPicker\Model\IconManifest:
  sources:
    tabler:
      enabled: true
      path: 'nobrainer/silverstripe-icon-picker: client/icons/tabler'
      label: 'Tabler'
      styles:
        outline: true
        filled: true
    custom:
      enabled: true
      path: 'app/icons'
      label: 'Custom'
  • path is either vendor/module: sub/path (a folder inside a Composer package) or a project-relative path.
  • styles splits a source into subfolders, each becoming its own tab (e.g. "Tabler – Outline"). Omit it for a flat source, like custom.
  • Nothing in the PHP or JS knows which libraries exist, so adding, disabling or repointing a source is a config change only.

Class names on rendered icons

The base class is configurable; each icon also gets a --<name> modifier built from its filename. Set it empty to render icons with no class at all.

Nobrainer\IconPicker\Model\IconManifest:
  css_class: 'my-icon'

It's baked into the manifest, so rebuild after changing it.

Overriding in a project

Target the same config class from your project's YAML and restate only what changes:

---
Name: app-icon-picker
After: '#nobrainer-icons'
---

Nobrainer\IconPicker\Model\IconManifest:
  sources:
    custom:
      path: 'themes/client/assets/icons/custom'
    tabler:
      styles:
        filled: false

That repoints the custom folder, keeping its enabled and label, and drops the Tabler Filled tab.

styles is a map rather than a list because Silverstripe's config merge only overrides string keys. Integer-keyed lists are appended to, so a list could be added to by a project but never trimmed.

Adding your own icons

Drop .svg files into the custom source's folder (app/icons by default), then rebuild:

sake dev/tasks/generate-icon-manifest

Filenames become the icon's name and what search matches on, so arrow-up-right.svg is findable as "arrow", "up" or "right".

How it works

IconManifest scans every enabled source for .svg files and caches a flat list of entries — id, name, group and cleaned markup. It's rebuilt on dev/build, or manually with sake dev/tasks/generate-icon-manifest, which is required after adding icon files or changing sources.

The CMS field is a hidden input holding a compact id (tabler:outline:home), enhanced into a React picker. Icon data reaches it in two requests, so neither scales with the size of the library:

  • /admin/icon-picker/manifest — the searchable index (id, group, name), no markup. Fetched once, the first time a modal opens. Search and tab filtering run against it client-side.
  • /admin/icon-picker/svgs?ids=… — markup for one grid page of icons. Anything already held is never re-fetched.

On the front end, DBIcon::forTemplate() looks the id up in the manifest and returns the inline SVG.

Development

The built bundle in client/dist/ is committed, so consuming projects never need Node. Only rebuild when changing files under client/:

npm install
npm run build   # or: npm run watch

Tests

Run from a project that has the module installed:

vendor/bin/phpunit --bootstrap vendor/silverstripe/framework/tests/bootstrap.php \
  vendor/nobrainer/silverstripe-icon-picker/tests/php/

Set SS_PHPUNIT_FLUSH=1 after changing config or adding a test class, otherwise the cached manifests serve stale values.

Troubleshooting

Icons don't appear after a config or file change. Run sake dev/build --flush. Silverstripe caches config and this module caches the manifest; neither picks up changes without a flush.

A source is missing entirely. dev/build and the manifest task print a warning naming any configured path that doesn't exist — check the output for a typo'd path.

Changes still don't apply after a flush. A long-running PHP-FPM worker can keep serving a stale manifest even after dev/build --flush or an HTTP ?flush=1 — most likely after replacing a large number of icon files at once, and can happen even with opcache.validate_timestamps enabled. If the icon count in the CMS doesn't match what a flush reported, restart PHP-FPM (or the container, in a local Docker/ddev setup) rather than flushing again. Production deploys that restart PHP-FPM per release aren't affected.

License

BSD-3-Clause — see LICENSE.

The bundled Tabler icons (v3.46.0) under client/icons/tabler/ are MIT licensed, © Paweł Kuna, and are redistributed with their notice intact — see client/icons/tabler/LICENSE and tabler/tabler-icons.