{
  "name": "TagInput",
  "category": "Forms",
  "subcategory": "Inputs",
  "description": "Input that turns entries into removable tag chips.",
  "thumbnail": "/preview-cards/tag-input.png",
  "hasDocsPage": true,
  "nugetPackage": "Lumeo",
  "files": [
    "UI/TagInput/TagInput.razor"
  ],
  "dependencies": [
    "list"
  ],
  "packageDependencies": [
    "Blazicons.Lucide"
  ],
  "cssVars": [
    "--color-accent",
    "--color-accent-foreground",
    "--color-border",
    "--color-destructive",
    "--color-foreground",
    "--color-input",
    "--color-muted",
    "--color-muted-foreground",
    "--color-popover",
    "--color-popover-foreground",
    "--color-ring",
    "--color-secondary",
    "--color-secondary-foreground",
    "--radius"
  ],
  "api": {
    "name": "TagInput",
    "category": "Forms",
    "subcategory": "Inputs",
    "description": "Input that turns entries into removable tag chips.",
    "nugetPackage": "Lumeo",
    "files": [
      "UI/TagInput/TagInput.razor"
    ],
    "namespace": "Lumeo",
    "inheritsFrom": null,
    "implements": [
      "IAsyncDisposable"
    ],
    "parameters": [
      {
        "name": "FormField",
        "type": "FormField.FormFieldContext?",
        "default": null,
        "description": null,
        "isCascading": true,
        "captureUnmatched": false
      },
      {
        "name": "Tags",
        "type": "List<TItem>?",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "TagsChanged",
        "type": "EventCallback<List<TItem>>",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "MaxTags",
        "type": "int",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "AllowDuplicates",
        "type": "bool",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "Placeholder",
        "type": "string?",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "Disabled",
        "type": "bool",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "Suggestions",
        "type": "List<TItem>?",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "Required",
        "type": "bool",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "Invalid",
        "type": "bool",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "ErrorText",
        "type": "string?",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "HelperText",
        "type": "string?",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "Label",
        "type": "string?",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "Name",
        "type": "string?",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "Class",
        "type": "string?",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "MaxTagsHelperText",
        "type": "string?",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "GetTagText",
        "type": "Func<TItem, string>?",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "GetSuggestionText",
        "type": "Func<TItem, string>?",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "TagTemplate",
        "type": "RenderFragment<TItem>?",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "SuggestionTemplate",
        "type": "RenderFragment<TItem>?",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "Separators",
        "type": "string[]",
        "default": "new[] { \",\" }",
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "ValidateInput",
        "type": "Func<string, string?>?",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "ValidateInputAsync",
        "type": "Func<string, Task<string?>>?",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "CreateTag",
        "type": "Func<string, TItem?>?",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "AdditionalAttributes",
        "type": "Dictionary<string, object>?",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": true
      }
    ],
    "events": [
      {
        "name": "TagsChanged",
        "type": "EventCallback<List<TItem>>",
        "description": null
      }
    ],
    "enums": [],
    "records": [],
    "cssVars": [
      "--color-accent",
      "--color-accent-foreground",
      "--color-border",
      "--color-destructive",
      "--color-foreground",
      "--color-input",
      "--color-muted",
      "--color-muted-foreground",
      "--color-popover",
      "--color-popover-foreground",
      "--color-ring",
      "--color-secondary",
      "--color-secondary-foreground",
      "--radius"
    ],
    "examples": [
      {
        "title": "Basic TagInput",
        "code": "<TagInput @bind-Tags=\"_tags\" Placeholder=\"Add tags...\" />"
      },
      {
        "title": "With Suggestions",
        "code": "<TagInput @bind-Tags=\"_tags\" Suggestions=\"@_suggestions\" />"
      },
      {
        "title": "Max Tags",
        "code": "<TagInput @bind-Tags=\"_tags\" MaxTags=\"3\" />"
      },
      {
        "title": "Generic TItem — User picker",
        "code": "public record UserOption(string Name, string Email);\n\nprivate List<UserOption> _users = new();\nprivate List<UserOption> _suggestions = new() { /* … */ };\n\n<TagInput TItem=\"UserOption\"\n          @bind-Tags=\"_users\"\n          Suggestions=\"@_suggestions\"\n          GetTagText=\"@(u => u.Name)\"\n          GetSuggestionText=\"@(u => $\"{u.Name} {u.Email}\")\"\n          CreateTag=\"@(text => new UserOption(text, text))\"\n          Separators=\"@(new[] { \",\", \";\", \" \" })\">\n    <TagTemplate Context=\"user\">\n        <span class=\"chip\">\n            <Avatar Initial=\"@user.Name[0]\" />\n            @user.Name\n        </span>\n    </TagTemplate>\n    <SuggestionTemplate Context=\"user\">\n        <Stack Direction=\"Row\" Gap=\"2\">\n            <Avatar Initial=\"@user.Name[0]\" />\n            <Stack Gap=\"0\">\n                <Text>@user.Name</Text>\n                <Text Color=\"muted\">@user.Email</Text>\n            </Stack>\n        </Stack>\n    </SuggestionTemplate>\n</TagInput>"
      }
    ],
    "subComponents": {},
    "parseFailed": false,
    "parseError": null
  },
  "$schema": "https://lumeo.nativ.sh/registry-component-schema.json",
  "key": "tag-input",
  "docsUrl": "https://lumeo.nativ.sh/components/tag-input",
  "sourceUrl": [
    {
      "file": "UI/TagInput/TagInput.razor",
      "url": "https://github.com/Brain2k-0005/Lumeo/tree/master/src/Lumeo/UI/TagInput/TagInput.razor"
    }
  ],
  "source": [
    {
      "path": "UI/TagInput/TagInput.razor",
      "content": "@namespace Lumeo\n@typeparam TItem\n@implements IAsyncDisposable\n\n@if (Label is not null && !InsideFormField)\n{\n    <label class=\"block text-sm font-medium text-foreground mb-1.5\">\n        @Label\n        @if (Required)\n        {\n            <span class=\"text-destructive ml-0.5\">*</span>\n        }\n    </label>\n}\n\n<div class=\"@ContainerClass\" @onclick=\"FocusInput\" @attributes=\"AdditionalAttributes\">\n    @if (Tags is { Count: > 0 })\n    {\n        @foreach (var tag in Tags)\n        {\n            if (TagTemplate is not null)\n            {\n                <div class=\"inline-flex items-center\" @key=\"tag\">\n                    @TagTemplate(tag)\n                    @if (!Disabled)\n                    {\n                        <button type=\"button\" class=\"ml-1 rounded-full hover:bg-foreground/20 focus:outline-none\" aria-label=\"@($\"Remove {GetTagTextSafe(tag)}\")\" @onclick=\"() => RemoveTag(tag)\" @onclick:stopPropagation=\"true\">\n                            <Blazicon Svg=\"Lucide.X\" class=\"h-3 w-3\" />\n                        </button>\n                    }\n                </div>\n            }\n            else\n            {\n                <span class=\"inline-flex items-center gap-1 rounded-md border border-border/40 bg-secondary px-2 py-0.5 text-xs font-medium text-secondary-foreground\" @key=\"tag\">\n                    @GetTagTextSafe(tag)\n                    @if (!Disabled)\n                    {\n                        <button type=\"button\" class=\"rounded-full hover:bg-foreground/20 focus:outline-none\" aria-label=\"@($\"Remove {GetTagTextSafe(tag)}\")\" @onclick=\"() => RemoveTag(tag)\" @onclick:stopPropagation=\"true\">\n                            <Blazicon Svg=\"Lucide.X\" class=\"h-3 w-3\" />\n                        </button>\n                    }\n                </span>\n            }\n        }\n    }\n\n    <div class=\"relative flex-1 min-w-[80px]\">\n        <input id=\"@_inputId\"\n               type=\"text\"\n               class=\"w-full bg-transparent text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed\"\n               placeholder=\"@(Tags is { Count: > 0 } ? \"\" : (Placeholder ?? L[\"TagInput.Placeholder\"]))\"\n               value=\"@_inputValue\"\n               name=\"@(Name ?? FormField?.Name)\"\n               aria-required=\"@(Required ? \"true\" : \"false\")\"\n               aria-invalid=\"@(EffectiveInvalid ? \"true\" : \"false\")\"\n               @oninput=\"OnInput\"\n               @onkeydown=\"OnKeyDown\"\n               @onfocus=\"() => _showSuggestions = FilteredSuggestions.Count > 0\"\n               disabled=\"@(IsAtMax || Disabled)\"\n               autocomplete=\"off\" />\n\n        @if (_showSuggestions && FilteredSuggestions.Count > 0)\n        {\n            <div id=\"@_suggestionsId\" class=\"fixed z-50 min-w-[150px] overflow-hidden rounded-md border border-border bg-popover p-1 text-popover-foreground shadow-lg animate-fade-in\">\n                @foreach (var suggestion in FilteredSuggestions)\n                {\n                    <button type=\"button\"\n                            class=\"flex w-full items-center rounded-sm px-2 py-1.5 text-sm hover:bg-accent hover:text-accent-foreground cursor-pointer transition-colors\"\n                            @onclick=\"() => SelectSuggestion(suggestion)\">\n                        @if (SuggestionTemplate is not null)\n                        {\n                            @SuggestionTemplate(suggestion)\n                        }\n                        else\n                        {\n                            @GetSuggestionTextSafe(suggestion)\n                        }\n                    </button>\n                }\n            </div>\n        }\n    </div>\n</div>\n\n@if (!InsideFormField)\n{\n    @if (_validationError is not null)\n    {\n        <p class=\"text-xs text-destructive mt-1.5\">@_validationError</p>\n    }\n    else if (EffectiveInvalid && ErrorText is not null)\n    {\n        <p class=\"text-xs text-destructive mt-1.5\">@ErrorText</p>\n    }\n    else if (IsAtMax && (MaxTagsHelperText is not null || MaxTags > 0))\n    {\n        <p class=\"text-xs text-muted-foreground mt-1.5\">@(MaxTagsHelperText ?? L[\"TagInput.MaxTagsReached\"])</p>\n    }\n    else if (HelperText is not null)\n    {\n        <p class=\"text-xs text-muted-foreground mt-1.5\">@HelperText</p>\n    }\n}\n\n@inject ComponentInteropService Interop\n@inject Lumeo.Services.Localization.ILumeoLocalizer L\n\n@code {\n    [CascadingParameter] private FormField.FormFieldContext? FormField { get; set; }\n\n    [Parameter] public List<TItem>? Tags { get; set; }\n    [Parameter] public EventCallback<List<TItem>> TagsChanged { get; set; }\n    [Parameter] public int MaxTags { get; set; }\n    [Parameter] public bool AllowDuplicates { get; set; }\n    [Parameter] public string? Placeholder { get; set; }\n    [Parameter] public bool Disabled { get; set; }\n    [Parameter] public List<TItem>? Suggestions { get; set; }\n    [Parameter] public bool Required { get; set; }\n    [Parameter] public bool Invalid { get; set; }\n    [Parameter] public string? ErrorText { get; set; }\n    [Parameter] public string? HelperText { get; set; }\n    [Parameter] public string? Label { get; set; }\n    [Parameter] public string? Name { get; set; }\n    [Parameter] public string? Class { get; set; }\n    [Parameter] public string? MaxTagsHelperText { get; set; }\n\n    [Parameter] public Func<TItem, string>? GetTagText { get; set; }\n    [Parameter] public Func<TItem, string>? GetSuggestionText { get; set; }\n    [Parameter] public RenderFragment<TItem>? TagTemplate { get; set; }\n    [Parameter] public RenderFragment<TItem>? SuggestionTemplate { get; set; }\n    [Parameter] public string[] Separators { get; set; } = new[] { \",\" };\n    [Parameter] public Func<string, string?>? ValidateInput { get; set; }\n    [Parameter] public Func<string, Task<string?>>? ValidateInputAsync { get; set; }\n    [Parameter] public Func<string, TItem?>? CreateTag { get; set; }\n\n    [Parameter(CaptureUnmatchedValues = true)] public Dictionary<string, object>? AdditionalAttributes { get; set; }\n\n    private bool EffectiveInvalid => Invalid || FormField?.HasError == true || _validationError is not null;\n    private bool InsideFormField => FormField is not null;\n\n    private string _inputValue = \"\";\n    private bool _showSuggestions;\n    private string? _validationError;\n    private readonly string _inputId = $\"taginput-{Guid.NewGuid():N}\";\n    private readonly string _suggestionsId = $\"taginput-suggestions-{Guid.NewGuid():N}\";\n    private bool _suggestionsRegistered;\n\n    private bool IsAtMax => MaxTags > 0 && (Tags?.Count ?? 0) >= MaxTags;\n\n    private string ContainerClass => $\"flex flex-wrap items-center gap-1.5 min-h-9 w-full rounded-md border {(EffectiveInvalid ? \"border-destructive focus-within:ring-destructive/20\" : \"border-input focus-within:ring-ring\")} bg-transparent px-3 py-1.5 text-sm transition-colors focus-within:ring-[1.5px] {(Disabled ? \"cursor-not-allowed opacity-50\" : \"cursor-text\")} {Class}\".Trim();\n\n    private string GetTagTextSafe(TItem item)\n        => GetTagText is not null ? GetTagText(item) : (item?.ToString() ?? \"\");\n\n    private string GetSuggestionTextSafe(TItem item)\n        => GetSuggestionText is not null ? GetSuggestionText(item)\n            : GetTagText is not null ? GetTagText(item)\n            : (item?.ToString() ?? \"\");\n\n    private List<TItem> FilteredSuggestions\n    {\n        get\n        {\n            if (Suggestions is null || string.IsNullOrWhiteSpace(_inputValue)) return [];\n            var tags = Tags ?? [];\n            return Suggestions\n                .Where(s => GetSuggestionTextSafe(s).Contains(_inputValue, StringComparison.OrdinalIgnoreCase)\n                            && (AllowDuplicates || !tags.Any(t => EqualityComparer<TItem>.Default.Equals(t, s))))\n                .Take(8)\n                .ToList();\n        }\n    }\n\n    private async Task OnInput(ChangeEventArgs e)\n    {\n        _inputValue = e.Value?.ToString() ?? \"\";\n        _validationError = null;\n\n        // Multi-separator splitting on every input event so paste handling works.\n        if (Separators is { Length: > 0 } && Separators.Any(sep => !string.IsNullOrEmpty(sep) && _inputValue.Contains(sep)))\n        {\n            var parts = _inputValue.Split(Separators, StringSplitOptions.RemoveEmptyEntries);\n            foreach (var raw in parts)\n            {\n                var part = raw.Trim();\n                if (string.IsNullOrEmpty(part)) continue;\n                await TryAddFreeText(part);\n            }\n            _inputValue = \"\";\n        }\n\n        _showSuggestions = FilteredSuggestions.Count > 0;\n    }\n\n    private async Task OnKeyDown(KeyboardEventArgs e)\n    {\n        switch (e.Key)\n        {\n            case \"Enter\":\n                if (!string.IsNullOrWhiteSpace(_inputValue))\n                {\n                    await TryAddFreeText(_inputValue.Trim());\n                    if (_validationError is null)\n                    {\n                        _inputValue = \"\";\n                        _showSuggestions = false;\n                    }\n                }\n                break;\n            case \"Backspace\":\n                if (string.IsNullOrEmpty(_inputValue) && Tags is { Count: > 0 })\n                {\n                    await RemoveTag(Tags[^1]);\n                }\n                break;\n            case \"Escape\":\n                _showSuggestions = false;\n                break;\n        }\n    }\n\n    private async Task TryAddFreeText(string text)\n    {\n        if (string.IsNullOrWhiteSpace(text)) return;\n\n        // Sync validation\n        if (ValidateInput is not null)\n        {\n            var err = ValidateInput(text);\n            if (err is not null)\n            {\n                _validationError = err;\n                return;\n            }\n        }\n\n        // Async validation\n        if (ValidateInputAsync is not null)\n        {\n            var err = await ValidateInputAsync(text);\n            if (err is not null)\n            {\n                _validationError = err;\n                return;\n            }\n        }\n\n        // Match existing suggestion (use suggestion text for comparison)\n        if (Suggestions is not null)\n        {\n            foreach (var s in Suggestions)\n            {\n                if (string.Equals(GetSuggestionTextSafe(s), text, StringComparison.OrdinalIgnoreCase))\n                {\n                    await AddTag(s);\n                    return;\n                }\n            }\n        }\n\n        // Convert free text → TItem\n        TItem created;\n        if (CreateTag is not null)\n        {\n            var maybe = CreateTag(text);\n            if (maybe is null) return;\n            created = maybe;\n        }\n        else if (typeof(TItem) == typeof(string))\n        {\n            created = (TItem)(object)text;\n        }\n        else\n        {\n            throw new InvalidOperationException(\n                \"Free-text input requires CreateTag callback for non-string TItem\");\n        }\n\n        await AddTag(created);\n    }\n\n    private async Task AddTag(TItem tag)\n    {\n        if (IsAtMax) return;\n\n        Tags ??= [];\n        if (!AllowDuplicates && Tags.Any(t => EqualityComparer<TItem>.Default.Equals(t, tag))) return;\n\n        Tags = [..Tags, tag];\n        _validationError = null;\n        await TagsChanged.InvokeAsync(Tags);\n    }\n\n    private async Task RemoveTag(TItem tag)\n    {\n        if (Tags is null) return;\n        Tags = Tags.Where(t => !EqualityComparer<TItem>.Default.Equals(t, tag)).ToList();\n        await TagsChanged.InvokeAsync(Tags);\n    }\n\n    private async Task SelectSuggestion(TItem suggestion)\n    {\n        await AddTag(suggestion);\n        _inputValue = \"\";\n        _showSuggestions = false;\n        await Interop.FocusElement(_inputId);\n    }\n\n    private async Task FocusInput()\n    {\n        if (!Disabled) await Interop.FocusElement(_inputId);\n    }\n\n    protected override async Task OnAfterRenderAsync(bool firstRender)\n    {\n        if (_showSuggestions && !_suggestionsRegistered)\n        {\n            try\n            {\n                await Interop.PositionFixed(_suggestionsId, _inputId, \"start\", false, \"bottom\");\n                await Interop.RegisterClickOutside(_suggestionsId, _inputId, async () =>\n                {\n                    _showSuggestions = false;\n                    await InvokeAsync(StateHasChanged);\n                });\n                _suggestionsRegistered = true;\n            }\n            catch (JSException) { /* element may not exist yet */ }\n        }\n        else if (!_showSuggestions && _suggestionsRegistered)\n        {\n            await CleanupSuggestions();\n        }\n    }\n\n    private async Task CleanupSuggestions()\n    {\n        try { await Interop.UnpositionFixed(_suggestionsId); } catch (JSDisconnectedException) { }\n        try { await Interop.UnregisterClickOutside(_suggestionsId); } catch (JSDisconnectedException) { }\n        _suggestionsRegistered = false;\n    }\n\n    public async ValueTask DisposeAsync()\n    {\n        if (_suggestionsRegistered) await CleanupSuggestions();\n    }\n}\n"
    }
  ],
  "slots": [
    {
      "name": "TagTemplate",
      "owner": "TagInput",
      "type": "RenderFragment<TItem>?",
      "description": null
    },
    {
      "name": "SuggestionTemplate",
      "owner": "TagInput",
      "type": "RenderFragment<TItem>?",
      "description": null
    }
  ],
  "serviceDependencies": [
    {
      "service": "ComponentInteropService",
      "namespace": ""
    },
    {
      "service": "ILumeoLocalizer",
      "namespace": "Lumeo.Services.Localization"
    }
  ],
  "cascadingDependencies": [
    {
      "name": "FormField",
      "type": "FormField.FormFieldContext?",
      "required": false
    }
  ],
  "relatedComponents": [
    {
      "name": "List",
      "reason": "used internally"
    }
  ],
  "keyboardInteractions": [],
  "tests": [
    "tests/Lumeo.Tests/Components/TagInput/TagInputGenericTests.cs",
    "tests/Lumeo.Tests/Components/TagInput/TagInputTests.cs"
  ],
  "mdSummary": "# TagInput\n\nInput that turns entries into removable tag chips.\n\n**Package:** `Lumeo` · **Category:** Forms/Inputs\n**Docs:** https://lumeo.nativ.sh/components/tag-input\n\n## Parameters\n| Owner | Name | Type | Default | Description |\n|---|---|---|---|---|\n| TagInput | FormField | `FormField.FormFieldContext?` | `` |  |\n| TagInput | Tags | `List<TItem>?` | `` |  |\n| TagInput | TagsChanged | `EventCallback<List<TItem>>` | `` |  |\n| TagInput | MaxTags | `int` | `` |  |\n| TagInput | AllowDuplicates | `bool` | `` |  |\n| TagInput | Placeholder | `string?` | `` |  |\n| TagInput | Disabled | `bool` | `` |  |\n| TagInput | Suggestions | `List<TItem>?` | `` |  |\n| TagInput | Required | `bool` | `` |  |\n| TagInput | Invalid | `bool` | `` |  |\n| TagInput | ErrorText | `string?` | `` |  |\n| TagInput | HelperText | `string?` | `` |  |\n| TagInput | Label | `string?` | `` |  |\n| TagInput | Name | `string?` | `` |  |\n| TagInput | Class | `string?` | `` |  |\n| TagInput | MaxTagsHelperText | `string?` | `` |  |\n| TagInput | GetTagText | `Func<TItem, string>?` | `` |  |\n| TagInput | GetSuggestionText | `Func<TItem, string>?` | `` |  |\n| TagInput | TagTemplate | `RenderFragment<TItem>?` | `` |  |\n| TagInput | SuggestionTemplate | `RenderFragment<TItem>?` | `` |  |\n| TagInput | Separators | `string[]` | `new[] { \",\" }` |  |\n| TagInput | ValidateInput | `Func<string, string?>?` | `` |  |\n| TagInput | ValidateInputAsync | `Func<string, Task<string?>>?` | `` |  |\n| TagInput | CreateTag | `Func<string, TItem?>?` | `` |  |\n| TagInput | AdditionalAttributes | `Dictionary<string, object>?` | `` |  |\n\n## Slots\n- `TagTemplate` (TagInput) — RenderFragment slot\n- `SuggestionTemplate` (TagInput) — RenderFragment slot\n\n## Events\n- `TagsChanged` (`EventCallback<List<TItem>>`)\n\n## Sub-components\n- (none)\n\n## Cascading values it expects\n- `FormField` (`FormField.FormFieldContext?`)\n\n## Services it injects\n- `ComponentInteropService` ()\n- `ILumeoLocalizer` (Lumeo.Services.Localization)\n\n## Keyboard\n—\n\n## Example\n```razor\n<TagInput />\n```\n\n## CSS variables\n`--color-accent`, `--color-accent-foreground`, `--color-border`, `--color-destructive`, `--color-foreground`, `--color-input`, `--color-muted`, `--color-muted-foreground`, `--color-popover`, `--color-popover-foreground`, `--color-ring`, `--color-secondary`, `--color-secondary-foreground`, `--radius`\n\n## Files\n- `UI/TagInput/TagInput.razor`\n"
}