Tag
componentA short label, which a button can take away.
Preview
Symfony
Code
<twig:Tag label="Symfony" :removable="true" name="tags[]" />
Symfony
Code
<twig:Tag label="Symfony" :removable="true" name="tags[]" />
Try it
Change the props; the example is rendered again by the server.
Symfony
<twig:Tag label="Symfony" :removable="true" name="tags[]" />
<twig:Tag label="Symfony" :removable="true" name="tags[]" />
Variants
removable
Whether it can be taken away.
Symfony
Symfony
Symfony
Symfony
Installation
php bin/console ux:install tag --kit=github.com/parler-haut-interagir-librement/ux-kit
Props
| Prop | Type | Default | Description |
|---|---|---|---|
removable |
bool |
false |
Whether it can be taken away. |
name |
string|null |
null |
The name of a hidden field carrying the tag in a form. |
value |
string|null |
null |
What the field and the event carry; the label when null. |
removeLabel |
string |
'Remove {label}' |
The name of the button, {label} replaced. |
Accessibility
The button is named by removeLabel and stays hidden without a script. Once the tag is gone, the focus goes to the tag beside it, or to the list. tag:remove can be cancelled, for a server to answer first.
Files to copy
assets/controllers/tag_controller.js
import { Controller } from '@hotwired/stimulus';
/*
* The button that takes a tag away. The event `tag:remove` can be cancelled, for a server to answer first. The focus
* goes to the tag beside it, or to the list once the last one is gone.
*/
export default class extends Controller {
static targets = ['remove'];
static values = { name: String };
connect() {
this.removeTarget.hidden = false;
}
remove() {
const event = this.dispatch('remove', { cancelable: true, detail: { name: this.nameValue } });
if (event.defaultPrevented) {
return;
}
const parent = this.element.parentElement;
const beside = this.#button(this.element.nextElementSibling) ?? this.#button(this.element.previousElementSibling);
this.element.remove();
if (beside) {
beside.focus();
} else if (parent) {
parent.tabIndex = -1;
parent.focus();
}
}
#button(tag) {
return tag?.querySelector(`[data-${this.identifier}-target~="remove"]`) ?? null;
}
}
templates/components/Tag.html.twig
{#-
# A short label, which a button can take away once a script runs. The event `tag:remove` can be cancelled; the focus
# goes to the tag beside it.
#}
{%- props
## string What the tag says.
label,
## bool Whether it can be taken away.
removable = false,
## string|null The name of a hidden field carrying the tag in a form.
name = null,
## string|null What the field and the event carry; the label when null.
value = null,
## string The name of the button, `{label}` replaced.
removeLabel = 'Remove {label}'
-%}
<span{{ attributes.defaults({class: 'isTag'}|merge(removable ? {'data-controller': 'tag', 'data-tag-name-value': value ?? label} : {})) }}>
<span class="isTagLabel">{{ label }}</span>
{%- if name is not null -%}<input type="hidden" name="{{ name }}" value="{{ value ?? label }}">{%- endif -%}
{%- if removable -%}<button type="button" class="isTagRemove" hidden aria-label="{{ removeLabel|replace({'{label}': label}) }}" data-tag-target="remove" data-action="tag#remove"><span aria-hidden="true">×</span></button>{%- endif -%}
</span>