diff --git a/src/BootstrapAdminUi/config/services/twig/extension.php b/src/BootstrapAdminUi/config/services/twig/extension.php
new file mode 100644
index 000000000..7f48f2759
--- /dev/null
+++ b/src/BootstrapAdminUi/config/services/twig/extension.php
@@ -0,0 +1,24 @@
+services();
+
+ $services->set('sylius_bootstrap_admin_ui.twig.extension.badge', BadgeExtension::class)
+ ->tag('twig.extension');
+};
+
diff --git a/src/BootstrapAdminUi/docs/badge-field.md b/src/BootstrapAdminUi/docs/badge-field.md
new file mode 100644
index 000000000..e2cf4b076
--- /dev/null
+++ b/src/BootstrapAdminUi/docs/badge-field.md
@@ -0,0 +1,265 @@
+# Badge Field
+
+The badge field allows you to render colored badges with icons in your Sylius grids.
+
+## Basic Usage
+
+### Using with Enums (Recommended)
+
+The cleanest way to use badges is by implementing `BadgeableInterface` on your enums:
+
+```php
+ 'Active',
+ self::INACTIVE => 'Inactive',
+ };
+ }
+
+ public function getColor(): string
+ {
+ return match ($this) {
+ self::ACTIVE => 'success',
+ self::INACTIVE => 'danger',
+ };
+ }
+
+ public function getIcon(): ?string
+ {
+ return match ($this) {
+ self::ACTIVE => 'heroicons:check',
+ self::INACTIVE => 'heroicons:x-mark',
+ };
+ }
+
+ public function getValue(): string
+ {
+ return $this->value;
+ }
+}
+```
+
+Then in your grid:
+
+```php
+use Sylius\Bundle\GridBundle\Builder\Field\TwigField;
+
+$gridBuilder->addField(
+ TwigField::create('status', '@SyliusBootstrapAdminUi/shared/grid/field/badge.html.twig')
+ ->setLabel('Status')
+ ->setSortable(true)
+);
+```
+
+### Using without Icons
+
+The `getIcon()` method is required by the interface, but you can return `null` if you don't want icons:
+
+```php
+ 'High Priority',
+ self::MEDIUM => 'Medium Priority',
+ self::LOW => 'Low Priority',
+ };
+ }
+
+ public function getColor(): string
+ {
+ return match ($this) {
+ self::HIGH => 'danger',
+ self::MEDIUM => 'warning',
+ self::LOW => 'info',
+ };
+ }
+
+ public function getIcon(): ?string
+ {
+ // No icons - just return null
+ return null;
+ }
+
+ public function getValue(): string
+ {
+ return $this->value;
+ }
+}
+```
+
+### Using with Simple Strings
+
+If you're displaying a simple string value:
+
+```php
+$gridBuilder->addField(
+ TwigField::create('type', '@SyliusBootstrapAdminUi/shared/grid/field/badge.html.twig')
+ ->setLabel('Type')
+);
+```
+
+### Using with Options Override
+
+You can override labels, colors, and icons using options:
+
+```php
+$gridBuilder->addField(
+ TwigField::create('status', '@SyliusBootstrapAdminUi/shared/grid/field/badge.html.twig')
+ ->setLabel('Status')
+ ->withOptions([
+ 'vars' => [
+ 'labels' => [
+ 'active' => 'Active',
+ 'inactive' => 'Inactive',
+ ],
+ 'colors' => [
+ 'active' => 'success',
+ 'inactive' => 'secondary',
+ ],
+ 'icons' => [
+ 'active' => 'heroicons:check-circle',
+ 'inactive' => 'heroicons:x-circle',
+ ],
+ ],
+ ])
+);
+```
+
+## BadgeableInterface
+
+The `BadgeableInterface` provides a contract for objects that can be displayed as badges:
+
+```php
+interface BadgeableInterface
+{
+ /**
+ * Returns the human-readable label for the badge.
+ */
+ public function getLabel(): string;
+
+ /**
+ * Returns the color variant for the badge.
+ * Should be one of: primary, secondary, success, danger, warning, info, light, dark
+ */
+ public function getColor(): string;
+
+ /**
+ * Returns the icon to display in the badge (optional).
+ * Return null if you don't want an icon.
+ * Can be a UX Icon name (e.g., 'heroicons:check') or a simple character/emoji.
+ */
+ public function getIcon(): ?string;
+
+ /**
+ * Returns the value for test attributes and data attributes.
+ */
+ public function getValue(): string;
+}
+```
+
+## Colors
+
+Available Bootstrap color variants:
+- `primary` - Blue
+- `secondary` - Gray
+- `success` - Green
+- `danger` - Red
+- `warning` - Yellow/Orange
+- `info` - Light blue
+- `light` - Light gray
+- `dark` - Dark gray/black
+
+## Icons
+
+Icons are **optional**. The `getIcon()` method returns `?string` (nullable).
+
+### No Icons
+Simply return `null` if you don't want icons:
+
+```php
+public function getIcon(): ?string
+{
+ return null; // No icon will be displayed
+}
+```
+
+### With Icons
+Icons can be:
+1. **UX Icons** (recommended): Use the format `bundle:icon-name`, e.g., `heroicons:check`
+2. **Simple characters/emojis**: e.g., `✓`, `⚠`, `🔥`
+
+Popular UX icon bundles:
+- `heroicons:` - Heroicons
+- `fa:` - Font Awesome
+- `bi:` - Bootstrap Icons
+- `lucide:` - Lucide Icons
+
+### Selective Icons
+You can also have icons for some cases only:
+
+```php
+public function getIcon(): ?string
+{
+ return match ($this) {
+ self::CRITICAL => '🔥', // Icon for critical
+ self::ERROR => '✗', // Icon for error
+ default => null, // No icon for other cases
+ };
+}
+```
+
+## Architecture
+
+The badge system consists of:
+
+1. **`BadgeableInterface`**: Contract for badge-displayable objects
+2. **`BadgeData`**: Value object that normalizes badge data from various sources
+3. **`BadgeExtension`**: Twig extension that provides the `sylius_badge_data()` function
+4. **Badge templates**: Twig macros for rendering badges
+
+This architecture ensures:
+- **Type safety**: No magic method calls via Twig's `is defined`
+- **Separation of concerns**: Logic in PHP, not Twig
+- **Flexibility**: Supports enums, arrays, and strings
+- **Testability**: Value objects and interfaces are easy to test
+
+## Testing
+
+Test attributes are automatically added to badges for E2E testing:
+
+```html
+
+ ✓ Healthy
+
+```
+
+The test attribute uses the `getValue()` method (for `BadgeableInterface` implementations) or the label as a fallback.
+
diff --git a/src/BootstrapAdminUi/src/Grid/BadgeData.php b/src/BootstrapAdminUi/src/Grid/BadgeData.php
new file mode 100644
index 000000000..1d57f67f2
--- /dev/null
+++ b/src/BootstrapAdminUi/src/Grid/BadgeData.php
@@ -0,0 +1,94 @@
+getLabel(),
+ color: $badgeable->getColor(),
+ icon: $badgeable->getIcon(),
+ value: $badgeable->getValue(),
+ );
+ }
+
+ /**
+ * Creates BadgeData from an array with optional overrides.
+ *
+ * @param array{label?: string, color?: string, icon?: string, value?: string} $data
+ * @param array{labels?: array, colors?: array, icons?: array} $overrides
+ */
+ public static function fromArray(array $data, array $overrides = []): self
+ {
+ $value = $data['value'] ?? $data['label'] ?? 'unknown';
+
+ return new self(
+ label: $overrides['labels'][$value] ?? $data['label'] ?? $value,
+ color: $overrides['colors'][$value] ?? $data['color'] ?? 'primary',
+ icon: $overrides['icons'][$value] ?? $data['icon'] ?? null,
+ value: $value,
+ );
+ }
+
+ /**
+ * Creates BadgeData from a simple string value.
+ */
+ public static function fromString(string $value, array $overrides = []): self
+ {
+ return new self(
+ label: $overrides['labels'][$value] ?? $value,
+ color: $overrides['colors'][$value] ?? 'grey',
+ icon: $overrides['icons'][$value] ?? null,
+ value: $value,
+ );
+ }
+
+ /**
+ * Creates BadgeData from mixed input (auto-detection).
+ *
+ * @param BadgeableInterface|array|string|null $data
+ * @param array{labels?: array, colors?: array, icons?: array} $overrides
+ */
+ public static function from(mixed $data, array $overrides = []): ?self
+ {
+ if ($data === null) {
+ return null;
+ }
+
+ if ($data instanceof BadgeableInterface) {
+ return self::fromBadgeable($data);
+ }
+
+ if (is_array($data)) {
+ return self::fromArray($data, $overrides);
+ }
+
+ if (is_string($data)) {
+ return self::fromString($data, $overrides);
+ }
+
+ // Fallback: convert to string
+ return self::fromString((string) $data, $overrides);
+ }
+}
+
diff --git a/src/BootstrapAdminUi/src/Grid/BadgeableInterface.php b/src/BootstrapAdminUi/src/Grid/BadgeableInterface.php
new file mode 100644
index 000000000..ca04570ec
--- /dev/null
+++ b/src/BootstrapAdminUi/src/Grid/BadgeableInterface.php
@@ -0,0 +1,38 @@
+ $options['vars']['labels'] ?? [],
+ 'colors' => $options['vars']['colors'] ?? [],
+ 'icons' => $options['vars']['icons'] ?? [],
+ ];
+
+ return BadgeData::from($data, $overrides);
+ }
+}
+
diff --git a/src/BootstrapAdminUi/templates/shared/grid/field/badge.html.twig b/src/BootstrapAdminUi/templates/shared/grid/field/badge.html.twig
new file mode 100644
index 000000000..9a7268b1a
--- /dev/null
+++ b/src/BootstrapAdminUi/templates/shared/grid/field/badge.html.twig
@@ -0,0 +1,3 @@
+{% import "@SyliusBootstrapAdminUi/shared/helper/field/badge.html.twig" as badge %}
+
+{{ badge.default(data, options|default({})) }}
\ No newline at end of file
diff --git a/src/BootstrapAdminUi/templates/shared/helper/field/badge.html.twig b/src/BootstrapAdminUi/templates/shared/helper/field/badge.html.twig
new file mode 100644
index 000000000..ee1a244b0
--- /dev/null
+++ b/src/BootstrapAdminUi/templates/shared/helper/field/badge.html.twig
@@ -0,0 +1,26 @@
+{#
+ Badge rendering macro for grid fields.
+
+ Accepts:
+ - Objects implementing BadgeableInterface (enums with getLabel(), getColor(), getIcon(), getValue())
+ - Arrays with keys: label, color, icon, value
+ - Simple strings
+
+ Options can override labels, colors, and icons via options.vars.labels, options.vars.colors, options.vars.icons
+#}
+{% macro default(data, options) %}
+ {% set badgeData = sylius_badge_data(data, options|default({})) %}
+
+ {% if badgeData %}
+
+ {% if badgeData.icon %}
+ {% if ':' in badgeData.icon %}
+ {{ ux_icon(badgeData.icon, {'class': 'icon icon-sm'}) }}
+ {% else %}
+ {{ badgeData.icon }}
+ {% endif %}
+ {% endif %}
+ {{ badgeData.label }}
+
+ {% endif %}
+{% endmacro %}
\ No newline at end of file