victoire/button-widget
A lightweight Laravel/PHP package from victoire that provides a reusable button widget for defining and rendering consistent action buttons in Blade views and UI components. Helps standardize button markup and simplify adding customizable buttons across your application.
victoire/button-widget (Victoire\Widget\ButtonBundle) is a single-widget bundle for the Victoire CMS: it gives editors a "Button" widget (label + link + link behaviour) that can be dropped into a page slot in the Victoire backend and rendered on the front end. It is a content-type plugin, not a standalone library — nothing works without Victoire Core and WidgetBundle installed.
Minimal steps
composer.json first. It is a 2019-era bundle and will pin php and symfony/* to 2.8/3.x ranges. If your app is Symfony 4+, Composer will refuse to resolve — in that case skip composer require and copy the bundle into src/ as a local scaffold instead (see Gotchas).composer require victoire/button-widget
app/AppKernel.php on Symfony 2/3):
new Victoire\Widget\ButtonBundle\VictoireWidgetButtonBundle(),
Register it after Victoire\Bundle\WidgetBundle\VictoireWidgetBundle so the widget tags/services resolve.BaseWidget and has its own table:
bin/console doctrine:schema:update --force
# or, preferred: doctrine:migrations:diff && doctrine:migrations:migrate
bin/console cache:clear
bin/console assets:install --symlink
Where to look first — five files tell you everything:
| File | Why |
|---|---|
Entity/WidgetButton.php |
The persisted fields (label, url, target, style…). Extend here. |
Form/WidgetButtonType.php |
The backend edit form. Extend here. |
Resources/views/Widget/button.html.twig |
Front-end markup. Override here. |
Resources/config/config.yml (or the bundle's DI Extension) |
Widget registration name/key. |
Resources/translations/*.yml |
Backend field labels. |
First use case: log into /admin, open a page, add a "Button" widget to a slot, fill in the label and link, save and publish. Confirm it renders. Then confirm the same button renders outside the CMS by rendering the widget from a Twig template, so you know both the slot path and the programmatic path work before you start customising.
1. Extending the widget with your own fields. The bundle is best treated as a template for your own button. Add the property + ORM column on WidgetButton, add the field to WidgetButtonType, then render it in the template:
// Entity/WidgetButton.php
/** @ORM\Column(name="icon", type="string", length=63, nullable=true) */
protected $icon;
// Form/WidgetButtonType.php
$builder->add('icon', TextType::class, ['required' => false, 'label' => 'button.form.icon.label']);
Generate the migration with doctrine:migrations:diff — do not hand-edit the schema, because the base widget table is managed by Victoire Core and easy to corrupt.
2. Overriding the front-end template (preferred over editing vendor code). Symfony's bundle override still works here:
app/Resources/VictoireWidgetButtonBundle/views/Widget/button.html.twig
Copy the vendor template verbatim first, verify it renders identically, then customise. This keeps you upgradeable and makes the diff obvious when you later vendor the bundle.
3. Rendering a widget from your own templates. Always go through Victoire's renderer instead of doing {{ widget.label }} manually — the renderer applies the CMS wrapper (needed for inline editing in the backend) and the widget cache. Use the same helper Victoire's slot templates use (inspect VictoireWidgetBundle's Resources/views/ for the exact function name in your Victoire version, e.g. victoire_widget_render/render_widget). Rendering the entity by hand silently breaks edit-mode on that page.
4. Link handling. If your Victoire build exposes a page/link picker, use it so internal links survive page-slug changes; otherwise store an absolute/relative URL string and normalise it in the entity getter:
public function getUrl(): ?string
{
$url = $this->url;
if ($url && 0 !== strpos($url, '/') && !preg_match('~^[a-z][a-z0-9+.-]*:~i', $url)) {
$url = 'https://'.$url; // bare "example.com" typed by an editor
}
return $url;
}
5. Presentation is a field, not a template fork. Rather than creating a new template per button colour/size, add style/size choice fields and map them to CSS classes in button.html.twig. One template, N variants — much easier to maintain than five overrides.
6. Composing with other widgets. A button is usually the CTA inside a "Text", "Jumbotron" or "Card" widget. Victoire widgets can be nested/positioned within slots; if you need the button inside a parent widget, prefer placing it in the parent's slot in the backend rather than embedding one widget's template in another — nested widgets keep their edit handles, hardcoded includes don't.
7. Cloning as a scaffold. Because it is tiny and complete (entity + form + template + registration + translations), this bundle is a good copy-paste starting point for WidgetCtaBundle, WidgetBadgeBundle, etc. Rename namespace, entity table, bundle class, translation keys and the widget registration key together — see the rename gotcha below.
src/ and own it:
"repositories": [{ "type": "path", "url": "packages/button-widget" }]
Then bump constraints yourself: getBlockPrefix() instead of getName() on the form type, Symfony\Component\HttpKernel\Bundle\Bundle unchanged, @= service syntax removed, Assetic removed.getName() vs getBlockPrefix(). Symfony 2 form types use getName(); Symfony 3+ use getBlockPrefix(). If the backend form renders the button fields in the wrong place or as a nested widget_button_type fieldset, this is why — rename the method, don't delete it.Resources/config/config.yml is what page/widget records reference. Renaming it after editors have created buttons orphans existing content (renders nothing, no error). Decide the name once; if you must rename, write a data migration over the widget table.WidgetButton extends Victoire's BaseWidget; if you copy the bundle, also copy/adapt the Doctrine mapping file (Resources/config/doctrine/*.orm.xml) and the bundle class name. Mismatch between entity class, table name and registration produces the classic "widget type not found" or a blank slot with no exception.{{ widget.label }} is auto-escaped; href="{{ widget.url }}" is not safe against javascript: — validate in the form with a Url/Regex constraint) and add rel="noopener noreferrer" whenever target is _blank.bin/console cache:clear and disable the Twig cache in dev, then verify you edited the overridden template and not the vendor one.Resources/translations/messages.*.yml; missing keys show the raw key in the admin. Add translations alongside every new field or the CMS UI looks broken to editors.bin/console debug:container | grep -i victoire to confirm services registered, bin/console config:dump-reference victoire_core (adapt the root key to your Victoire version) to see how widgets are registered, doctrine:mapping:info to confirm WidgetButton is mapped, and {{ dump(widget) }} inside the template to inspect actual field values.Widget/button.html.twig? Almost every "button not showing" bug is one of those four.data-widget-id/edit-mode markup around widgets; replacing the whole template with a bare <a> can break inline editing and page caching. Keep the outer wrapper the bundle provides unless you know the renderer chain.How can I help you explore Laravel packages today?