[GH-ISSUE #229] Allow for <select> to have no currently selected option, or allow to hide that the option is selected when the <select> is not focused #825

Open
opened 2026-03-14 08:42:03 +03:00 by kerem · 7 comments
Owner

Originally created by @pizzaisdavid on GitHub (Oct 23, 2025).
Original GitHub issue: https://github.com/anomalyco/opentui/issues/229

I have four <select> elements side-by-side:

Image

In the screenshot, the second <select> is focused.

I would like the possibility to have no selected option when an select isn't focused.

Meaning: no triangle, no yellow text, no blue background.

An alternative could be that the selected option looks different when the <select> is not focused compared to when it is focused.

Below is my sample code. You can change the focused <select> using the left and right arrow keys.

import { render, useKeyboard } from "@opentui/react"
import type { SelectOption } from "@opentui/core"
import { useState } from "react"

function App() {
  const [focused, setFocused] = useState<0 | 1 | 2 | 3>(0)

  useKeyboard((key) => {
    if (key.name === "left") {
      if (focused === 0) {
        // none
      } else if (focused === 1) {
        setFocused(0)
      } else if (focused === 2) {
        setFocused(1)
      } else if (focused === 3) {
        setFocused(2)
      }
    } else if (key.name === "right") {
      if (focused === 0) {
        setFocused(1)
      } else if (focused === 1) {
        setFocused(2)
      } else if (focused === 2) {
        setFocused(3)
      } else if (focused === 3) {
        // none
      }
    }
  })

  const options: SelectOption[] = [
    { name: "Option 1", description: "Option 1 description", value: "opt1" },
    { name: "Option 2", description: "Option 2 description", value: "opt2" },
    { name: "Option 3", description: "Option 3 description", value: "opt3" },
  ]

  return (
    <box style={{ flexDirection: 'row', width: '100%' }}>
      <box style={{ border: true, height: '100%', width: '25%' }}>
        <select
          style={{ height: '100%', width: '100%' }}
          options={options}
          focused={focused === 0}
        />
      </box>

      <box style={{ border: true, height: '100%', width: '25%' }}>
        <select
          style={{ height: '100%', width: '100%' }}
          options={options}
          focused={focused === 1}
        />
      </box>

      <box style={{ border: true, height: '100%', width: '25%' }}>
        <select
          style={{ height: '100%', width: '100%' }}
          options={options}
          focused={focused === 2}
        />
      </box>

      <box style={{ border: true, height: '100%', width: '25%' }}>
        <select
          style={{ height: '100%', width: '100%' }}
          options={options}
          focused={focused === 3}
        />
      </box>
    </box>
  )
}

render(<App />)
Originally created by @pizzaisdavid on GitHub (Oct 23, 2025). Original GitHub issue: https://github.com/anomalyco/opentui/issues/229 I have four `<select>` elements side-by-side: <img width="1483" height="749" alt="Image" src="https://github.com/user-attachments/assets/107d4932-656a-4dda-8fa5-e91a227cf4c9" /> In the screenshot, the second `<select>` is focused. I would like the possibility to have no selected option when an select isn't focused. Meaning: no triangle, no yellow text, no blue background. An alternative could be that the selected option looks different when the `<select>` is not focused compared to when it is focused. Below is my sample code. You can change the focused `<select>` using the left and right arrow keys. ``` import { render, useKeyboard } from "@opentui/react" import type { SelectOption } from "@opentui/core" import { useState } from "react" function App() { const [focused, setFocused] = useState<0 | 1 | 2 | 3>(0) useKeyboard((key) => { if (key.name === "left") { if (focused === 0) { // none } else if (focused === 1) { setFocused(0) } else if (focused === 2) { setFocused(1) } else if (focused === 3) { setFocused(2) } } else if (key.name === "right") { if (focused === 0) { setFocused(1) } else if (focused === 1) { setFocused(2) } else if (focused === 2) { setFocused(3) } else if (focused === 3) { // none } } }) const options: SelectOption[] = [ { name: "Option 1", description: "Option 1 description", value: "opt1" }, { name: "Option 2", description: "Option 2 description", value: "opt2" }, { name: "Option 3", description: "Option 3 description", value: "opt3" }, ] return ( <box style={{ flexDirection: 'row', width: '100%' }}> <box style={{ border: true, height: '100%', width: '25%' }}> <select style={{ height: '100%', width: '100%' }} options={options} focused={focused === 0} /> </box> <box style={{ border: true, height: '100%', width: '25%' }}> <select style={{ height: '100%', width: '100%' }} options={options} focused={focused === 1} /> </box> <box style={{ border: true, height: '100%', width: '25%' }}> <select style={{ height: '100%', width: '100%' }} options={options} focused={focused === 2} /> </box> <box style={{ border: true, height: '100%', width: '25%' }}> <select style={{ height: '100%', width: '100%' }} options={options} focused={focused === 3} /> </box> </box> ) } render(<App />) ```
Author
Owner

@pizzaisdavid commented on GitHub (Oct 23, 2025):

I am trying to make a kanban board I only want one option that looks currently selected (at most) because is where the "cursor" is located. I hope that makes some sense.

<!-- gh-comment-id:3437023064 --> @pizzaisdavid commented on GitHub (Oct 23, 2025): I am trying to make a kanban board I only want one `option` that looks currently selected (at most) because is where the "cursor" is located. I hope that makes some sense.
Author
Owner

@kommander commented on GitHub (Oct 23, 2025):

Yeah that makes sense.

<!-- gh-comment-id:3438110724 --> @kommander commented on GitHub (Oct 23, 2025): Yeah that makes sense.
Author
Owner

@pizzaisdavid commented on GitHub (Oct 23, 2025):

A bit more research:

The behavior that Textual does by default: when a <select> has a selected option, and the <select> becomes unfocused, the color of the selected option changes.

Demo:

Image

<!-- gh-comment-id:3439501051 --> @pizzaisdavid commented on GitHub (Oct 23, 2025): A bit more research: The behavior that Textual does by default: when a `<select>` has a selected option, and the `<select>` becomes unfocused, the color of the selected option changes. Demo: ![Image](https://github.com/user-attachments/assets/b5a52940-d1cd-450c-8156-07f438be08f4)
Author
Owner

@msmps commented on GitHub (Oct 30, 2025):

@pizzaisdavid this can also be solved as a consumer by setting the selectedTextColor, selectedBackgroundColor and selectedDescriptionColor properties.

Example 👇
import type { SelectOption, SelectRenderableOptions } from "@opentui/core"
import { render, useKeyboard } from "@opentui/react"
import { useState } from "react"

function App() {
  const [focused, setFocused] = useState<"left" | "right">("left")

  useKeyboard((key) => {
    if (key.name === "right" || key.name === "left") {
      setFocused((prev) => (prev === "left" ? "right" : "left"))
    }
  })

  const options: SelectOption[] = [
    { name: "Option 1", description: "Description #1", value: "opt_1" },
    { name: "Option 2", description: "Description #2", value: "opt_2" },
    { name: "Option 3", description: "Description #3", value: "opt_3" },
  ]

  const focusedStyle = {
    selectedBackgroundColor: "#334455",
    selectedTextColor: "#FFFF00",
    selectedDescriptionColor: "#CCCCCC",
  } satisfies SelectRenderableOptions

  const unfocusedStyle = {
    selectedBackgroundColor: "gray",
    selectedTextColor: "white",
    selectedDescriptionColor: "white",
  } satisfies SelectRenderableOptions

  return (
    <box style={{ flexDirection: "row", width: "100%" }}>
      <box style={{ border: true, flexGrow: 1 }}>
        <select
          style={{
            height: "100%",
            width: "100%",
            ...(focused !== "left" ? unfocusedStyle : focusedStyle),
          }}
          options={options}
          focused={focused === "left"}
        />
      </box>

      <box style={{ border: true, flexGrow: 1 }}>
        <select
          style={{
            height: "100%",
            width: "100%",
            ...(focused !== "right" ? unfocusedStyle : focusedStyle),
          }}
          options={options}
          focused={focused === "right"}
        />
      </box>
    </box>
  )
}

render(<App />)
Image
<!-- gh-comment-id:3469131823 --> @msmps commented on GitHub (Oct 30, 2025): @pizzaisdavid this can also be solved as a consumer by setting the `selectedTextColor`, `selectedBackgroundColor` and `selectedDescriptionColor` properties. <details> <summary>Example 👇</summary> ```tsx import type { SelectOption, SelectRenderableOptions } from "@opentui/core" import { render, useKeyboard } from "@opentui/react" import { useState } from "react" function App() { const [focused, setFocused] = useState<"left" | "right">("left") useKeyboard((key) => { if (key.name === "right" || key.name === "left") { setFocused((prev) => (prev === "left" ? "right" : "left")) } }) const options: SelectOption[] = [ { name: "Option 1", description: "Description #1", value: "opt_1" }, { name: "Option 2", description: "Description #2", value: "opt_2" }, { name: "Option 3", description: "Description #3", value: "opt_3" }, ] const focusedStyle = { selectedBackgroundColor: "#334455", selectedTextColor: "#FFFF00", selectedDescriptionColor: "#CCCCCC", } satisfies SelectRenderableOptions const unfocusedStyle = { selectedBackgroundColor: "gray", selectedTextColor: "white", selectedDescriptionColor: "white", } satisfies SelectRenderableOptions return ( <box style={{ flexDirection: "row", width: "100%" }}> <box style={{ border: true, flexGrow: 1 }}> <select style={{ height: "100%", width: "100%", ...(focused !== "left" ? unfocusedStyle : focusedStyle), }} options={options} focused={focused === "left"} /> </box> <box style={{ border: true, flexGrow: 1 }}> <select style={{ height: "100%", width: "100%", ...(focused !== "right" ? unfocusedStyle : focusedStyle), }} options={options} focused={focused === "right"} /> </box> </box> ) } render(<App />) ``` </details> <img width="912" height="744" alt="Image" src="https://github.com/user-attachments/assets/ba023066-8816-4f57-a415-9ae4bf2f7d26" />
Author
Owner

@aprogramq commented on GitHub (Nov 4, 2025):

Can I take it?

<!-- gh-comment-id:3487801720 --> @aprogramq commented on GitHub (Nov 4, 2025): Can I take it?
Author
Owner

@aprogramq commented on GitHub (Nov 4, 2025):

Image

import type { SelectOption } from "@opentui/core"
import { useState } from "react"

function App() {
  const [focused, setFocused] = useState<0 | 1 | 2 | 3>(0)

  useKeyboard((key) => {
    if (key.name === "left") {
      if (focused === 0) {
        // none
      } else if (focused === 1) {
        setFocused(0)
      } else if (focused === 2) {
        setFocused(1)
      } else if (focused === 3) {
        setFocused(2)
      }
    } else if (key.name === "right") {
      if (focused === 0) {
        setFocused(1)
      } else if (focused === 1) {
        setFocused(2)
      } else if (focused === 2) {
        setFocused(3)
      } else if (focused === 3) {
        // none
      }
    }
  })

  const options: SelectOption[] = [
    { name: "Option 1", description: "Option 1 description", value: "opt1" },
    { name: "Option 2", description: "Option 2 description", value: "opt2" },
    { name: "Option 3", description: "Option 3 description", value: "opt3" },
  ]

  return (
    <box style={{ flexDirection: "row", width: "100%" }}>
      <box style={{ border: true, height: "100%", width: "25%" }}>
        <select
          style={{ height: "100%", width: "100%" }}
          options={options}
          focused={focused === 0}
          selectedIndex={focused !== 0 ? -1 : 0}
        />
      </box>

      <box style={{ border: true, height: "100%", width: "25%" }}>
        <select
          style={{ height: "100%", width: "100%" }}
          options={options}
          focused={focused === 1}
          selectedIndex={focused !== 1 ? -1 : 0}
        />
      </box>

      <box style={{ border: true, height: "100%", width: "25%" }}>
        <select
          style={{ height: "100%", width: "100%" }}
          options={options}
          focused={focused === 2}
          selectedIndex={focused !== 2 ? -1 : 0}
        />
      </box>

      <box style={{ border: true, height: "100%", width: "25%" }}>
        <select
          style={{ height: "100%", width: "100%" }}
          options={options}
          focused={focused === 3}
          selectedIndex={focused !== 3 ? -1: 0}
        />
      </box>
    </box>
  );
}

render(<App />);
<!-- gh-comment-id:3488359505 --> @aprogramq commented on GitHub (Nov 4, 2025): ![Image](https://github.com/user-attachments/assets/ae536687-274d-49d8-ac4b-4bd530525177) ```import { render, useKeyboard } from "@opentui/react" import type { SelectOption } from "@opentui/core" import { useState } from "react" function App() { const [focused, setFocused] = useState<0 | 1 | 2 | 3>(0) useKeyboard((key) => { if (key.name === "left") { if (focused === 0) { // none } else if (focused === 1) { setFocused(0) } else if (focused === 2) { setFocused(1) } else if (focused === 3) { setFocused(2) } } else if (key.name === "right") { if (focused === 0) { setFocused(1) } else if (focused === 1) { setFocused(2) } else if (focused === 2) { setFocused(3) } else if (focused === 3) { // none } } }) const options: SelectOption[] = [ { name: "Option 1", description: "Option 1 description", value: "opt1" }, { name: "Option 2", description: "Option 2 description", value: "opt2" }, { name: "Option 3", description: "Option 3 description", value: "opt3" }, ] return ( <box style={{ flexDirection: "row", width: "100%" }}> <box style={{ border: true, height: "100%", width: "25%" }}> <select style={{ height: "100%", width: "100%" }} options={options} focused={focused === 0} selectedIndex={focused !== 0 ? -1 : 0} /> </box> <box style={{ border: true, height: "100%", width: "25%" }}> <select style={{ height: "100%", width: "100%" }} options={options} focused={focused === 1} selectedIndex={focused !== 1 ? -1 : 0} /> </box> <box style={{ border: true, height: "100%", width: "25%" }}> <select style={{ height: "100%", width: "100%" }} options={options} focused={focused === 2} selectedIndex={focused !== 2 ? -1 : 0} /> </box> <box style={{ border: true, height: "100%", width: "25%" }}> <select style={{ height: "100%", width: "100%" }} options={options} focused={focused === 3} selectedIndex={focused !== 3 ? -1: 0} /> </box> </box> ); } render(<App />); ```
Author
Owner

@jettptacek commented on GitHub (Jan 24, 2026):

Can we close this issue since https://github.com/anomalyco/opentui/pull/269 has been merged?

<!-- gh-comment-id:3793702124 --> @jettptacek commented on GitHub (Jan 24, 2026): Can we close this issue since https://github.com/anomalyco/opentui/pull/269 has been merged?
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/opentui#825
No description provided.