# SelectInput

*Create dropdown [menus](../../01_Onboarding/02_Concepts/09_Navigation.md) with single or multiple selection capabilities, option grouping, and custom rendering for user choices.*

The `SelectInput` [widget](../../01_Onboarding/02_Concepts/03_Widgets.md) provides a dropdown menu for selecting items from a predefined list of options. It supports single
and multiple selections, option grouping, and custom rendering of option items.

## Basic Usage

Here's a simple example of a `SelectInput` with a few options. Use [Size](../../04_ApiReference/IvyShared/Size.md) for `.Width(Size.Full())` to make the select fill available space:

```csharp
public class SelectVariantDemo : ViewBase
{
    public override object? Build()
    {
        var favLang = UseState("C#");
        return favLang.ToSelectInput(["C#", "Java", "Go", "JavaScript", "F#", "Kotlin", "VB.NET", "Rust"])
                         .Variant(SelectInputs.Select)
                         .WithField()
                         .Label("Select your favourite programming language")
                         .Width(Size.Full());
    }    
}
```

## Multiple Selection

Multiple selection is automatically enabled when you use a collection type (array, List, etc.) as your state. The framework automatically detects this and enables multi-select functionality.

`SelectInput` supports three variants: **Select** (dropdown), **List** (checkboxes), and **Toggle** (button toggles). Multi-select works with all variants and data types. Here's an example demonstrating different combinations:

```csharp
public class MultiSelectDemo : ViewBase
{
    private enum ProgrammingLanguages
    {
        CSharp,
        Java,
        Python,
        JavaScript,
        Go,
        Rust
    }
    
    public override object? Build()
    {
        var languagesSelect = UseState<ProgrammingLanguages[]>([]);
        var stringArray = UseState<string[]>([]);
        var intArray = UseState<int[]>([]);
        
        var languageOptions = typeof(ProgrammingLanguages).ToOptions();
        var stringOptions = new[] { "Option A", "Option B", "Option C", "Option D" };
        var intOptions = new[] { 1, 2, 3, 4, 5 }.ToOptions();
        
        return Layout.Vertical()
            | Text.InlineCode("Select Variant (Enum)")
            | languagesSelect.ToSelectInput(languageOptions)
                .Variant(SelectInputs.Select)
                .Placeholder("Choose languages...")
            
            | Text.InlineCode("List Variant (String Array)")
            | stringArray.ToSelectInput(stringOptions.ToOptions())
                .Variant(SelectInputs.List)
            
            | Text.InlineCode("Toggle Variant (Integer Array)")
            | intArray.ToSelectInput(intOptions)
                .Variant(SelectInputs.Toggle);
    }
}
```

## Event Handling

Handle change events and create dynamic option lists that respond to user selections:

```csharp
public class EventHandlingDemo : ViewBase
{
    private static readonly Dictionary<string, string[]> CategoryOptions = new()
    {
        ["Programming"] = new[]{"C#", "Java", "Python", "JavaScript"},
        ["Design"] = new[]{"Photoshop", "Figma", "Sketch"},
        ["Database"] = new[]{"SQL Server", "PostgreSQL", "MongoDB"}
    };
    
    public override object? Build()
    {
        var selectedCategory = UseState("Programming");
        var selectedSkill = UseState("");
        var showInfo = UseState(false);
        
        var categoryOptions = CategoryOptions.Keys.ToOptions();
        var skillOptions = CategoryOptions[selectedCategory.Value].ToOptions();
        
        return Layout.Vertical()
            | Layout.Grid().Columns(2)
                | selectedCategory.ToSelectInput(categoryOptions)
                    .Placeholder("Choose a category...")
                    .WithField()
                    .Label("Category:")
                
                | new SelectInput<string>(
                    value: selectedSkill.Value,
                    onChange: e =>
                    {
                        selectedSkill.Set(e.Value);
                        showInfo.Set(!string.IsNullOrEmpty(e.Value));
                    },
                    skillOptions)
                    .Placeholder("Select a skill...")
                    .WithField()
                    .Label("Skill:")
            
            | (showInfo.Value 
                ? Text.Block($"Selected: {selectedCategory.Value} → {selectedSkill.Value}") 
                : null);
    }
}
```

## Styling and States

Customize the `SelectInput` with various styling options:

```csharp
public class SelectStylingDemo : ViewBase
{
    public override object? Build()
    {
        var normalSelect = UseState("");
        var invalidSelect = UseState("");
        var disabledSelect = UseState("");
        
        var options = new[] { "Option 1", "Option 2", "Option 3" };
        
        return Layout.Vertical()
            | normalSelect.ToSelectInput(options)
                .Placeholder("Choose an option...")
                .WithField()
                .Label("Normal SelectInput:")
            
            | invalidSelect.ToSelectInput(options)
                .Placeholder("This has an error...")
                .Invalid("This field is required")
                .WithField()
                .Label("Invalid SelectInput:")
            
            | disabledSelect.ToSelectInput(options)
                .Placeholder("This is disabled...")
                .Disabled(true)
                .WithField()
                .Label("Disabled SelectInput:");
    }
}
```

> **tip:** Use Select for single choice dropdowns, List for multiple selection with checkboxes, and Toggle for visual button-based selection. The List variant is particularly useful for [forms](../../01_Onboarding/02_Concepts/13_Forms.md) where users need to select multiple options.


## API

[View Source: SelectInput.cs](https://github.com/Ivy-Interactive/Ivy-Framework/blob/main/src/Ivy/Widgets/Inputs/SelectInput.cs)

### Constructors

| Signature |
|-----------|
| `new SelectInput<TValue>(IAnyState state, IEnumerable<IAnyOption> options, string placeholder = null, bool disabled = false, SelectInputs variant = SelectInputs.Select, bool selectMany = false)` |
| `new SelectInput<TValue>(TValue value, Func<Event<IInput<TValue>, TValue>, ValueTask> onChange, IEnumerable<IAnyOption> options, string placeholder = null, bool disabled = false, SelectInputs variant = SelectInputs.Select, bool selectMany = false)` |
| `new SelectInput<TValue>(TValue value, Action<Event<IInput<TValue>, TValue>> onChange, IEnumerable<IAnyOption> options, string placeholder = null, bool disabled = false, SelectInputs variant = SelectInputs.Select, bool selectMany = false)` |
| `new SelectInput<TValue>(IEnumerable<IAnyOption> options, string placeholder = null, bool disabled = false, SelectInputs variant = SelectInputs.Select, bool selectMany = false)` |
| `ToSelectInput(IAnyState state, IEnumerable<IAnyOption> options = null, string placeholder = null, bool disabled = false, SelectInputs variant = SelectInputs.Select)` |


### Properties

| Name | Type | Setters |
|------|------|---------|
| `Disabled` | `bool` | `Disabled` |
| `Height` | `Size` | - |
| `Invalid` | `string` | `Invalid` |
| `Nullable` | `bool` | `Nullable` |
| `Options` | `IAnyOption[]` | - |
| `Placeholder` | `string` | `Placeholder` |
| `Scale` | `Scale?` | - |
| `SelectMany` | `bool` | - |
| `Separator` | `char` | `Separator` |
| `Value` | `TValue` | - |
| `Variant` | `SelectInputs` | `Variant` |
| `Visible` | `bool` | - |
| `Width` | `Size` | - |


### Events

| Name | Type | Handlers |
|------|------|----------|
| `OnBlur` | `Func<Event<IAnyInput>, ValueTask>` | `HandleBlur` |
| `OnChange` | `Func<Event<IInput<TValue>, TValue>, ValueTask>` | - |




## Examples


### Ordering System

A comprehensive example showing different SelectInput [variants](../../01_Onboarding/02_Concepts/17_Theming.md) in a real-world scenario:

```csharp
public class CoffeeShopDemo: ViewBase
{
    private static readonly Dictionary<string, List<string>> CoffeeAccompaniments = new()
    {
        ["Cappuccino"] = new List<string> 
        { 
            "Cinnamon powder", "Cocoa powder", "Sugar cubes", "Biscotti", 
            "Cantuccini", "Amaretti", "Whipped cream" 
        },
        ["Espresso"] = new List<string> 
        { 
            "Lemon peel", "Sugar cubes", "Water", "Chocolate square", 
            "Praline", "Biscotti" 
        },
        ["Latte"] = new List<string> 
        { 
            "Vanilla syrup", "Caramel syrup", "Hazelnut syrup", "Cocoa powder", 
            "Cinnamon", "Croissant", "Muffin", "Steamed milk art" 
        },
        ["Mocha"] = new List<string> 
        { 
            "Whipped cream", "Chocolate shavings", "Cocoa powder", "Marshmallows", 
            "Cinnamon stick", "Caramel drizzle", "Vanilla syrup" 
        }
    };
    
    string[] coffeeSizes = new string[]{"Short", "Tall", "Grande", "Venti"};
    
    public override object? Build()
    {
        var coffee = UseState("Cappuccino");
        var coffeeSize = UseState("Tall");
        var selectedCondiments = UseState(new string[]{});
        var previousCoffee = UseState("Cappuccino");
        
        if (previousCoffee.Value != coffee.Value)
        {
            selectedCondiments.Set(new string[]{});
            previousCoffee.Set(coffee.Value);
        }
        
        var coffeeSizeMenu = coffeeSize.ToSelectInput(coffeeSizes)
                                       .Variant(SelectInputs.List);
        var availableCondiments = CoffeeAccompaniments[coffee.Value];
        
        var condimentMenu = selectedCondiments.ToSelectInput(availableCondiments.ToOptions())
            .Variant(SelectInputs.Toggle);
        
        var orderSummary = BuildOrderSummary(coffee.Value, coffeeSize.Value, selectedCondiments.Value);
        
        return Layout.Vertical()
                | Layout.Grid().Columns(2)
                    | coffee.ToSelectInput(CoffeeAccompaniments.Keys.ToOptions())
                            .WithField()
                            .Label("Coffee Type:")
                    
                    | coffeeSizeMenu
                        .WithField()
                        .Label("Size:")
                    
                    | condimentMenu
                        .WithField()
                        .Label("Condiments:")
                    
                | new Icon(Icons.Coffee) 
                | Text.Block(orderSummary);
    }
    
    private string BuildOrderSummary(string coffee, string size, string[] condiments)
    {
        var summary = $"{size} {coffee}";
        
        if (condiments.Length > 0)
        {
            if(condiments.Length == 1)
            {
                summary += $" with {condiments[0]}";
            }
            else
            {                  
                 summary += " with " + condiments
                                                 .Take(condiments.Length - 1)
                                                 .Aggregate((a,b) =>  a + ", " + b)
                                                 + " and " + condiments[condiments.Length - 1];
            }
        }
        
        return summary;
    }
}
```