{"id":15092972,"url":"https://github.com/blazored/typeahead","last_synced_at":"2025-08-12T00:31:34.103Z","repository":{"id":34907223,"uuid":"187533664","full_name":"Blazored/Typeahead","owner":"Blazored","description":"Typeahead control for Blazor applications","archived":true,"fork":false,"pushed_at":"2023-11-14T20:57:00.000Z","size":9896,"stargazers_count":447,"open_issues_count":67,"forks_count":105,"subscribers_count":15,"default_branch":"main","last_synced_at":"2025-07-14T06:32:41.317Z","etag":null,"topics":["blazor","blazor-applications","blazored","csharp","hacktoberfest","nuget","typeahead","typeahead-component","typeahead-control"],"latest_commit_sha":null,"homepage":"https://blazored.github.io/Typeahead/","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Blazored.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null},"funding":{"github":["chrissainty"]}},"created_at":"2019-05-19T22:16:55.000Z","updated_at":"2025-06-29T23:38:34.000Z","dependencies_parsed_at":"2024-04-10T02:56:08.309Z","dependency_job_id":"60e7bfba-7fc0-4cea-bcd0-0c7e2fa20d47","html_url":"https://github.com/Blazored/Typeahead","commit_stats":{"total_commits":152,"total_committers":22,"mean_commits":6.909090909090909,"dds":0.6973684210526316,"last_synced_commit":"a317c8be64e1580d3d61c80dfb8286523d2bccf6"},"previous_names":[],"tags_count":15,"template":false,"template_full_name":null,"purl":"pkg:github/Blazored/Typeahead","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Blazored%2FTypeahead","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Blazored%2FTypeahead/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Blazored%2FTypeahead/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Blazored%2FTypeahead/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Blazored","download_url":"https://codeload.github.com/Blazored/Typeahead/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Blazored%2FTypeahead/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":269980802,"owners_count":24507270,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-11T02:00:10.019Z","response_time":75,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["blazor","blazor-applications","blazored","csharp","hacktoberfest","nuget","typeahead","typeahead-component","typeahead-control"],"created_at":"2024-09-25T11:02:21.746Z","updated_at":"2025-08-12T00:31:33.725Z","avatar_url":"https://github.com/Blazored.png","language":"C#","funding_links":["https://github.com/sponsors/chrissainty"],"categories":[],"sub_categories":[],"readme":"# Blazored Typeahead\r\nTypeahead control for Blazor applications.\r\n\r\n[![Nuget version](https://img.shields.io/nuget/v/Blazored.Typeahead.svg?logo=nuget)](https://www.nuget.org/packages/Blazored.Typeahead/)\r\n[![Nuget downloads](https://img.shields.io/nuget/dt/Blazored.Typeahead?logo=nuget)](https://www.nuget.org/packages/Blazored.Typeahead/)\r\n![Build \u0026 Test Main](https://github.com/Blazored/Typeahead/workflows/Build%20\u0026%20Test%20Main/badge.svg)\r\n\r\n![Screenshot](screenshot.png)\r\n\r\n### Installing\r\n\r\nYou can install from NuGet using the following command:\r\n\r\n`Install-Package Blazored.Typeahead`\r\n\r\nOr via the Visual Studio package manger.\r\n\r\n### Setup\r\nBlazor Server applications will need to include the following CSS and JS files in their `_Host.cshtml` .\r\n\r\nBlazor Client applications will need to include the following CSS and JS files in their `Index.html` .\r\n\r\nIn the `head` tag add the following CSS.\r\n\r\n```html\r\n\u003clink href=\"_content/Blazored.Typeahead/blazored-typeahead.css\" rel=\"stylesheet\" /\u003e\r\n```\r\n\r\nThen add the JS script at the bottom of the page using the following script tag.\r\n\r\n```html\r\n\u003cscript src=\"_content/Blazored.Typeahead/blazored-typeahead.js\"\u003e\u003c/script\u003e\r\n```\r\n\r\nI would also suggest adding the following using statement to your main `_Imports.razor` to make referencing the component a bit easier.\r\n\r\n```cs\r\n@using Blazored.Typeahead\r\n```\r\n\r\n## Usage\r\nThe component can be used standalone or as part of a form. When used in a form the control fully integrates with Blazors forms and authentication system.\r\n\r\nBelow is a list of all the options available on the Typeahead.\r\n\r\n**Templates**\r\n\r\n- `ResultTemplate` (Required) - Allows the user to define a template for a result in the results list\r\n- `SelectedTemplate` (Required) - Allows the user to define a template for a selected item\r\n- `HelpTemplate` - Allows the user to define a template to show when the `MinimumLength` to perform a search hasn't been reached\r\n- `NotFoundTemplate` - Allows the user to define a template when no items are found\r\n- `FooterTemplate` - Allows the user to define a template which is displayed at the end of the results list\r\n\r\n**Parameters**\r\n\r\n- `MinimumLength` (Optional - Default: 1) - Minimum number of characters before starting a search\r\n- `Debounce` (Optional - Default: 300) - Time to wait after last keypress before starting a search\r\n- `MaximumSuggestions` (Optional - Default: 10) - Controls the amount of suggestions which are shown\r\n- `Disabled` (Optional - Default: `false`) - Marks the control as disabled and stops any interaction\r\n- `EnableDropDown` (Optional - Default: `false`) - Allows the control to behave as a dropdown\r\n- `DisableClear` (Optional - Default : `false`) - Hides the clear button from the Typeahead. Users can still change the selection by clicking on the current selection and typing however, they can't clear the control entirely.'\r\n- `ShowDropDownOnFocus` (Optional - Default: `false`) - When enabled, will show the suggestions dropdown automatically when the control is in search mode. If the control has a current value then the user would need to press the enter key first to enter search mode.\r\n- `StopPropagation` (Optional - Default: `false`) - Control the StopPropagation behavior of the input of this component. See https://docs.microsoft.com/en-us/aspnet/core/blazor/components?view=aspnetcore-3.1#stop-event-propagation\r\n- `PreventDefault` (Optional - Default: `false`) - Control the PreventDefault behavior of the input of this component. See https://docs.microsoft.com/en-us/aspnet/core/blazor/components?view=aspnetcore-3.1#prevent-default-actions\r\n\r\nThe control also requires a `SearchMethod` to be provided with the following signature `Task\u003cIEnumerable\u003cTItem\u003e\u003e(string searchText)`. The control will invoke this method \r\npassing the text the user has typed into the control. You can then query your data source and return the result as an `IEnumerable` for the control to render.\r\n\r\nIf you wish to bind the result of the selection in the control to a different type than the type used in the search this is also possible. For example, if you passed in a list\r\nof `Person` but when a `Person` was selected you wanted the control to bind to an `int` value which might be the `Id` of the selected `Person`, you can achieve this by providing\r\na `ConvertMethod` The convert method will be invoked by the control when a selection is made and will be passed the type selected. The method will need to handle the conversion\r\nand return the new type.\r\n\r\nIf you want to allow adding an item based on the search when no items have been found, you can achieve this by providing the `AddItemOnEmptyResultMethod` as a parameter.\r\nThis method will make the `NotFoundTemplate` selectable the same way a item would normally be, and will be invoked when the user selects the `NotFoundTemplate`.\r\nThis method passes the `SearchText` and expects a new item to be returned.\r\n\r\n### Local Data Example\r\n```cs\r\n\u003cEditForm Model=\"MyFormModel\" OnValidSubmit=\"HandlValidSubmit\"\u003e\r\n    \u003cBlazoredTypeahead SearchMethod=\"SearchFilms\"\r\n                            @bind-Value=\"MyFormModel.SelectedFilm\"\u003e\r\n        \u003cSelectedTemplate\u003e\r\n            @context.Title\r\n        \u003c/SelectedTemplate\u003e\r\n        \u003cResultTemplate\u003e\r\n            @context.Title (@context.Year)\r\n        \u003c/ResultTemplate\u003e\r\n    \u003c/BlazoredTypeahead\u003e\r\n    \u003cValidationMessage For=\"@(() =\u003e MyFormModel.SelectedFilm)\" /\u003e\r\n\u003c/EditForm\u003e\r\n\r\n@code {\r\n\r\n    [Parameter] protected IEnumerable\u003cFilm\u003e Films { get; set; }\r\n\r\n    private async Task\u003cIEnumerable\u003cFilm\u003e\u003e SearchFilms(string searchText) \r\n    {\r\n        return await Task.FromResult(Films.Where(x =\u003e x.Title.ToLower().Contains(searchText.ToLower())).ToList());\r\n    }\r\n\r\n}\r\n```\r\nIn the example above, the component is setup with the minimum requirements. You must provide a method which has the following signature `Task\u003cIEnumerable\u003cT\u003e MethodName(string searchText)`, to the `SearchMethod` parameter. The control will call this method with the current search text everytime the debounce timer expires (default: 300ms). You must also set a value for the `Value` parameter. This will be populated with the item selected from the search results. As this version of the control is integrated with Blazors built-in forms and validation, it must be wrapped in a `EditForm` component.\r\n\r\nThe component requires two templates to be provided...\r\n\r\n- `SelectedTemplate`\r\n- `ResultTemplates`\r\n\r\nThe `SelectedTemplate` is used to display the selected item and the `ResultTemplate` is used to display each result in the search list.\r\n\r\n\r\n### Remote Data Example\r\n\r\n```cs\r\n@inject HttpClient httpClient\r\n\r\n\u003cBlazoredTypeahead SearchMethod=\"@SearchFilms\"\r\n                   @bind-Value=\"@SelectedFilm\"\r\n                   Debounce=\"500\"\u003e\r\n    \u003cSelectedTemplate\u003e\r\n        @context.Title\r\n    \u003c/SelectedTemplate\u003e\r\n    \u003cResultTemplate\u003e\r\n        @context.Title (@context.Year)\r\n    \u003c/ResultTemplate\u003e\r\n    \u003cNotFoundTemplate\u003e\r\n        Sorry, there weren't any search results.\r\n    \u003c/NotFoundTemplate\u003e\r\n\u003c/BlazoredTypeahead\u003e\r\n\r\n@code {\r\n\r\n    [Parameter] protected IEnumerable\u003cFilm\u003e Films { get; set; }\r\n\r\n    private async Task\u003cIEnumerable\u003cFilm\u003e\u003e SearchFilms(string searchText) \r\n    {\r\n        var response = await httpClient.GetJsonAsync\u003cIEnumerable\u003cFilm\u003e\u003e($\"https://allfilms.com/api/films/?title={searchText}\");\r\n        return response;\r\n    }\r\n\r\n}\r\n```\r\nBecause you provide the search method to the component, making a remote call is really straight-forward. In this example, the `Debounce` parameter has been upped to 500ms and the `NotFoundTemplate` has been specified.\r\n\r\n### Subscribing to changes in selected values\r\nIt is common to want to be able to know when a value bound to the Typeahead changes. To do this you can't use the standard `@bind-Value` or `@bind-Values` syntax, you must handle the change event manually. To do this you must specify the following parameters:\r\n\r\n- Value\r\n- ValueChanged\r\n- ValueExpression\r\n- TValue \u0026 TItem (these are not always necessary)\r\n\r\nThe code below shows an example of how these parameters should be used.\r\n\r\n```razor\r\n\u003cBlazoredTypeahead SearchMethod=\"SearchPeople\"\r\n                   TValue=\"Result\"\r\n                   TItem=\"Result\"\r\n                   Value=\"selectedResult\"\r\n                   ValueChanged=\"SelectedResultChanged\" \r\n                   ValueExpression=\"@(() =\u003e selectedResult)\"\r\n                   placeholder=\"Search by name...\"\u003e\r\n\u003c/BlazoredTypeahead\u003e\r\n\r\n@code {\r\n    private MovieCredits movieCredits;\r\n    private Result selectedResult;\r\n\r\n    private async Task\u003cIEnumerable\u003cResult\u003e\u003e SearchPeople(string searchText)\r\n    {\r\n        var search = await client.SearchPerson(searchText);\r\n        return search.Results;\r\n    }\r\n\r\n    private async Task SelectedResultChanged(Result result)\r\n    {\r\n        selectedResult = result;\r\n        movieCredits = await client.GetPersonMovieCredits(result.Id);\r\n    }\r\n}\r\n```\r\n\r\n### Using complex types but only binding to a single property\r\nThere are times when you will want to use complex types with the Typeahead but only bind a certain property of that type. For example, you may want to search against a `Person` but once a person is selected, only bind to it's `Id` property. In order to do this you will need to implement the following:\r\n\r\n```razor\r\n\u003cBlazoredTypeahead SearchMethod=\"GetPeopleLocal\"\r\n                   ConvertMethod=\"ConvertPerson\"\r\n                   @bind-Value=\"SelectedPersonId\"\r\n                   placeholder=\"Search by first name...\"\u003e\r\n    \u003cSelectedTemplate Context=\"personId\"\u003e\r\n        @{\r\n            var selectedPerson = LoadSelectedPerson(personId);\r\n\r\n            \u003ctext\u003e@selectedPerson?.Firstname @selectedPerson?.Lastname\u003c/text\u003e\r\n        }\r\n    \u003c/SelectedTemplate\u003e\r\n    \u003cResultTemplate Context=\"person\"\u003e\r\n        @person.Firstname @person.Lastname (Id: @person.Id)\r\n    \u003c/ResultTemplate\u003e\r\n\u003c/BlazoredTypeahead\u003e\r\n\r\n@code {\r\n    private List\u003cPerson\u003e People = new List\u003cPerson\u003e();\r\n\r\n    protected override void OnInitialized()\r\n    {\r\n        People.AddRange(new List\u003cPerson\u003e() {\r\n            new Person() { Id = 1, Firstname = \"Martelle\", Lastname = \"Cullon\" },\r\n            new Person() { Id = 2, Firstname = \"Zelda\", Lastname = \"Abrahamsson\" },\r\n            new Person() { Id = 3, Firstname = \"Benedetta\", Lastname = \"Posse\" }\r\n        });\r\n    }\r\n\r\n    private async Task\u003cIEnumerable\u003cPerson\u003e\u003e GetPeopleLocal(string searchText)\r\n    {\r\n        return await Task.FromResult(People.Where(x =\u003e x.Firstname.ToLower().Contains(searchText.ToLower())).ToList());\r\n    }\r\n\r\n    private int? ConvertPerson(Person person) =\u003e person?.Id;\r\n\r\n    private Person LoadSelectedPerson(int? id) =\u003e People.FirstOrDefault(p =\u003e p.Id == id);\r\n}\r\n\r\n```\r\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fblazored%2Ftypeahead","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fblazored%2Ftypeahead","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fblazored%2Ftypeahead/lists"}