Search by

wilr / silverstripe-togglegroup-field

wilr

Toggle group form fields for the Silverstripe CMS, styled after the shadcn/ui toggle group component. Provides drop-in replacements for OptionsetField and CheckboxSetField.

Package info

github.com/wilr/silverstripe-togglegroup-field

Type:silverstripe-vendormodule

pkg:composer/wilr/silverstripe-togglegroup-field

Statistics

Installs: 13

Dependents: 1

Suggesters: 0

Stars: 0

Open Issues: 0

2.1.0 2026-09-17 00:00 UTC

This package is auto-updated.

Last update: 2026-09-17 04:23:15 UTC


README

Form field subclasses that render as a segmented "toggle group" control - styled after shadcn/ui's toggle group component

  • instead of a list of radio buttons or checkboxes.
  • ToggleGroupField is a drop-in replacement for OptionsetField (single selection)
  • ToggleGroupSetField is a drop-in replacement for CheckboxSetField (multiple selection)

Both fields work anywhere a standard getCMSFields() field can be used, including inside Elemental content blocks.

ToggleGroupField and ToggleGroupSetField on a standard Page

Above: ToggleGroupField (single-selection "Alignment") and ToggleGroupSetField (multi-selection "Available days") added to a Page's getCMSFields().

ToggleGroupField and ToggleGroupSetField inside an Elemental block

Above: the same two fields added to a DNADesign\Elemental\Models\BaseElement subclass's getCMSFields(), rendering identically inside the Elemental block editor.

Requirements

  • PHP ^8.3
  • silverstripe/framework ^6
  • dnadesign/silverstripe-elemental ^6 (optional, only if you want to use the fields inside Elemental blocks)

For Silverstripe CMS 5, use the 1 branch (^1).

Installation

composer require wilr/silverstripe-togglegroup-field

Then run a dev/build and flush (?flush=1).

Usage

Both fields extend their core Silverstripe equivalent, so they accept the same constructor arguments, support the same saving behaviour (including has_one/many_many relations and DBMultiEnum/JSON-encoded values), the same validation, and the same readonly/disabled transformations. Only the rendering changes.

Single selection - ToggleGroupField

use Wilr\ToggleGroupField\Forms\ToggleGroupField;

public function getCMSFields()
{
    $fields = parent::getCMSFields();

    $fields->addFieldToTab('Root.Main', ToggleGroupField::create(
        'Alignment',
        'Alignment',
        [
            'left' => 'Left',
            'center' => 'Center',
            'right' => 'Right',
        ]
    ));

    return $fields;
}

Multiple selection - ToggleGroupSetField

use Wilr\ToggleGroupField\Forms\ToggleGroupSetField;

public function getCMSFields()
{
    $fields = parent::getCMSFields();

    $fields->addFieldToTab('Root.Main', ToggleGroupSetField::create(
        'Days',
        'Available days',
        [
            'mon' => 'Mon',
            'tue' => 'Tue',
            'wed' => 'Wed',
            'thu' => 'Thu',
            'fri' => 'Fri',
        ]
    ));

    return $fields;
}

Inside an Elemental block

Add the fields to a DNADesign\Elemental\Models\BaseElement subclass exactly as you would on a Page:

use DNADesign\Elemental\Models\BaseElement;
use Wilr\ToggleGroupField\Forms\ToggleGroupField;

class MyElement extends BaseElement
{
    private static $db = [
        'Alignment' => 'Varchar',
    ];

    public function getCMSFields()
    {
        $fields = parent::getCMSFields();

        $fields->addFieldToTab('Root.Main', ToggleGroupField::create(
            'Alignment',
            'Alignment',
            [
                'left' => 'Left',
                'center' => 'Center',
                'right' => 'Right',
            ]
        ));

        return $fields;
    }
}

Elemental's block editor is a schema-driven, React-rendered form (unlike a classic Page.getCMSFields(), which is rendered server-side as plain HTML). For that context the module registers ToggleGroupField and ToggleGroupSetField React components (client/dist/js/toggle-group-react.js, loaded in the CMS via LeftAndMain.extra_requirements_javascript). They render the same markup as the templates, so styling and icons are identical, and are bound to the form state so values save inline and in the "add block" modal.

Style variants

ToggleGroupField::create(/* ... */)
    ->setButtonsBlock(true)  // stretch options to fill the available width
    ->setButtonsSmall(true); // render a more compact button size

Icons on option labels

Both fields can show a Silverstripe CMS font icon and/or custom SVG beside (or instead of) each option's text label.

CMS font icons

Use the same icon identifiers as FormAction::setIcon() (with or without the font-icon- prefix). Browse the available glyphs in vendor/silverstripe/admin/client/src/font/icons-reference.html after installing silverstripe/admin.

ToggleGroupField::create(
    'Alignment',
    'Alignment',
    [
        'left' => 'Left',
        'center' => 'Center',
        'right' => 'Right',
    ]
)->setOptionIcons([
    'left' => 'angle-left',
    'center' => 'dot-3',
    'right' => 'angle-right',
]);

Custom SVG

Pass a full <svg>...</svg> element, or one or more shape elements such as <path> (automatically wrapped in a 24×24 viewBox SVG that inherits the button colour via currentColor). SVG is rendered as trusted HTML - only pass markup you control.

ToggleGroupField::create(
    'Layout',
    'Layout',
    [
        'list' => 'List',
        'grid' => 'Grid',
    ]
)->setOptionIcons([
    'list' => '<path d="M4 6h16M4 12h16M4 18h16" stroke="currentColor" stroke-width="2" fill="none"/>',
    'grid' => '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><rect x="3" y="3" width="8" height="8"/><rect x="13" y="3" width="8" height="8"/><rect x="3" y="13" width="8" height="8"/><rect x="13" y="13" width="8" height="8"/></svg>',
]);

Icon-only buttons

Hide the text visually while keeping it available to screen readers through the associated <label>:

ToggleGroupField::create(/* ... */)
    ->setOptionIcons([
        'left' => 'angle-left',
        'center' => 'dot-3',
        'right' => 'angle-right',
    ])
    ->setIconsOnly(true);

Accessibility

Each option is rendered as a real <input type="radio"> or <input type="checkbox"> paired with a <label> - the toggle-look buttons are just CSS applied to native, always-keyboard-accessible form controls (visually hidden with a clip-based technique rather than display: none, so they remain focusable and readable by screen readers). This means:

  • The field works correctly with no JavaScript at all.
  • Tab moves focus in and out of the group as normal.
  • For ToggleGroupField, arrow keys move the selection between options - this is native browser behaviour for radio inputs sharing the same name.
  • For ToggleGroupSetField, arrow keys move focus (without changing selection) between checkboxes, matching the WAI-ARIA toggle group keyboard pattern. This is implemented in client/src/js/toggle-group.js since checkboxes have no native roving-focus behaviour.
  • The wrapping element uses role="radiogroup" (ToggleGroupField) or role="group" (ToggleGroupSetField) to describe the relationship between the options to assistive technology.

Running the tests

composer install
vendor/bin/phpunit

License

See LICENSE