diff --git a/packages/core/src/components/Select.ts b/packages/core/src/components/Select.ts
new file mode 100644
index 0000000..5451382
--- /dev/null
+++ b/packages/core/src/components/Select.ts
@@ -0,0 +1,180 @@
+import {
+  h,
+  provide,
+  inject,
+  defineComponent,
+  onMounted,
+  onUpdated,
+  onUnmounted,
+  Fragment,
+  computed,
+  Comment,
+} from '@vue/runtime-core'
+import type {
+  PropType,
+  InjectionKey,
+  VNode,
+  ComputedRef,
+} from '@vue/runtime-core'
+import { TuiBox } from './Box'
+import { TuiText } from './Text'
+import { scheduleUpdateSymbol } from '../injectionSymbols'
+import { onKeyData } from '../composables/keyboard'
+import type { ForegroundColorProp } from '../renderer/textColor'
+import { KeyDataEventKey } from '../input/types'
+
+export interface TuiSelectIndicator {
+  /**
+   * Figure of indicator.
+   *
+   * @default ❯
+   */
+  figure: string
+  /**
+   * Indicator figure color.
+   *
+   * @default ❯
+   */
+  color: ForegroundColorProp
+}
+
+export const tuiSelectSymbol = Symbol('vue-termui:select') as InjectionKey<{
+  activeName: ComputedRef<string | number>
+  indicator: TuiSelectIndicator
+}>
+
+export const TuiSelect = defineComponent({
+  name: 'TuiSelect',
+  props: {
+    modelValue: {
+      type: [String, Number],
+      required: true,
+    },
+    indicator: {
+      type: Object as PropType<TuiSelectIndicator>,
+      default: {
+        figure: '❯',
+        color: 'blue',
+      },
+    },
+    submitKey: {
+      type: [String, Array] as PropType<KeyDataEventKey | KeyDataEventKey[]>,
+      required: false,
+      // Space key
+      default: [' '],
+    },
+  },
+  emit: ['update:modelValue', 'submit'],
+  setup(props, { slots, emit }) {
+    const children = computed(() => {
+      const defaultSlots = slots.default?.()
+      const children = defaultSlots
+        ?.filter((child) => child.type !== Comment)
+        ?.reduce(
+          (nodeList: VNode[], node: VNode) =>
+            node.type === Fragment
+              ? [...nodeList, ...(node.children as VNode[])]
+              : [...nodeList, node],
+          []
+        )
+
+      return children ?? []
+    })
+
+    const activeName = computed(() => props.modelValue)
+    const activeIndex = computed(() =>
+      children.value?.findIndex(
+        (item) => item?.props?.value === activeName.value
+      )
+    )
+
+    const scheduleUpdate = inject(scheduleUpdateSymbol)!
+
+    onMounted(scheduleUpdate)
+
+    onUpdated(scheduleUpdate)
+
+    onUnmounted(scheduleUpdate)
+
+    provide(tuiSelectSymbol, {
+      activeName: activeName,
+      indicator: props.indicator,
+    })
+
+    const stopDownInput = onKeyData(['ArrowDown', 'ArrowRight'], () => {
+      const index =
+        activeIndex.value! + 1 > children.value.length! - 1
+          ? 0
+          : activeIndex.value! + 1
+      emit('update:modelValue', children.value?.[index]?.props?.value)
+    })
+
+    const stopUpInput = onKeyData(['ArrowUp', 'ArrowLeft'], () => {
+      const index =
+        activeIndex.value! - 1 < 0
+          ? children.value.length! - 1
+          : activeIndex.value! - 1
+      emit('update:modelValue', children.value?.[index]?.props?.value)
+    })
+
+    const stopSubmitInput = onKeyData(props.submitKey, () => {
+      stopDownInput()
+      stopUpInput()
+      stopSubmitInput()
+      emit('submit', activeName.value, activeIndex.value)
+    })
+
+    return () => {
+      return h(
+        TuiBox,
+        {
+          flexDirection: 'column',
+        },
+        () => slots.default?.()
+      )
+    }
+  },
+})
+
+export const TuiOption = defineComponent({
+  name: 'TuiOption',
+
+  props: {
+    label: {
+      type: [String, Number],
+      required: false,
+    },
+    value: [String, Number],
+  },
+
+  setup(props, { slots }) {
+    const tuiSelectRoot = inject(tuiSelectSymbol)!
+    return () => {
+      const isActive = props.value === tuiSelectRoot.activeName.value
+      const { color, figure } = tuiSelectRoot.indicator
+      return h(TuiBox, {}, () => [
+        // Indicator
+        h(
+          TuiText,
+          {
+            color,
+          },
+          () => (isActive ? figure : ' ') + ' '
+        ),
+        // Option
+        slots
+          .default?.({
+            isActive,
+          })
+          ?.filter((child) => child.type !== Comment)?.length
+          ? h(
+              Fragment,
+              slots.default?.({
+                isActive,
+              })
+            )
+          : h(TuiText, () => props.label),
+      ])
+    }
+  },
+})
diff --git a/packages/core/src/components/index.ts b/packages/core/src/components/index.ts
index 392921c..dd9080b 100644
--- a/packages/core/src/components/index.ts
+++ b/packages/core/src/components/index.ts
@@ -3,6 +3,7 @@ export { TuiTextTransform } from './TextTransform'
 export { TuiNewline } from './Newline'
 export { TuiApp } from './App'
 export { TuiBox } from './Box'
+export { TuiSelect, TuiOption } from './Select'
 
 export { TuiLink } from './Link'
 // export { default as TuiInput } from './Input.vue'
diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts
index bc9dee5..1412fd9 100644
--- a/packages/core/src/index.ts
+++ b/packages/core/src/index.ts
@@ -7,6 +7,8 @@ export {
   TuiNewline,
   TuiLink,
   TuiTextTransform,
+  TuiSelect,
+  TuiOption,
 } from './components'
 
 export { render } from './renderer'
diff --git a/packages/core/src/renderer/textColor.ts b/packages/core/src/renderer/textColor.ts
index 6eda09e..aba6711 100644
--- a/packages/core/src/renderer/textColor.ts
+++ b/packages/core/src/renderer/textColor.ts
@@ -1,6 +1,9 @@
 import chalk from 'chalk'
-
+import type { ForegroundColor } from 'chalk'
 type ColorType = 'foreground' | 'background'
+import type { LiteralUnion } from '../utils'
+
+export type ForegroundColorProp = LiteralUnion<ForegroundColor, string>
 
 const RGB_LIKE_REGEX = /^(rgb|hsl|hsv|hwb)\(\s?(\d+),\s?(\d+),\s?(\d+)\s?\)$/
 const ANSI_REGEX = /^(ansi|ansi256)\(\s?(\d+)\s?\)$/
diff --git a/packages/playground/components.d.ts b/packages/playground/components.d.ts
index 8737c06..8023ff6 100644
--- a/packages/playground/components.d.ts
+++ b/packages/playground/components.d.ts
@@ -11,6 +11,8 @@ declare module '@vue/runtime-core' {
     Br: typeof import('vue-termui')['TuiNewline']
     Div: typeof import('vue-termui')['TuiBox']
     Link: typeof import('vue-termui')['TuiLink']
+    Option: typeof import('vue-termui')['TuiOption']
+    Select: typeof import('vue-termui')['TuiSelect']
     Span: typeof import('vue-termui')['TuiText']
     Text: typeof import('vue-termui')['TuiText']
   }
diff --git a/packages/playground/src/SelectDemo.vue b/packages/playground/src/SelectDemo.vue
new file mode 100644
index 0000000..07cf230
--- /dev/null
+++ b/packages/playground/src/SelectDemo.vue
@@ -0,0 +1,38 @@
+<script setup lang="ts">
+import { ref } from 'vue-termui'
+const value = ref('vue')
+const options = [
+  {
+    value: 'vue',
+    label: 'Vue',
+  },
+  {
+    value: 'react',
+    label: 'React',
+  },
+  {
+    value: 'svelte',
+    label: 'Svelte',
+  },
+  {
+    value: 'solid',
+    label: 'Solid',
+  },
+]
+
+function submit(name: string, index: number) {
+  console.log(name, index)
+}
+</script>
+
+<template borderStyle="round">
+  <Select v-model="value" @submit="submit">
+    <Option :value="item.value" v-for="(item, index) in options" :key="index">
+      <template v-slot="{ isActive }">
+        <Box flexDirection="column">
+          <Text :dimmed="isActive" :underline="isActive">{{ item.label }}</Text>
+        </Box>
+      </template>
+    </Option>
+  </Select>
+</template>
diff --git a/packages/playground/src/main.ts b/packages/playground/src/main.ts
index bbaa6b2..b564075 100644
--- a/packages/playground/src/main.ts
+++ b/packages/playground/src/main.ts
@@ -1,12 +1,13 @@
 // import devtools from '@vue/devtools'
 // import devtools from '@vue/devtools/node'
 import { createApp } from 'vue-termui'
-import App from './Focusables.vue'
+// import App from './Focusables.vue'
 // import App from './Fragments.vue'
 // import App from './CenteredDemo.vue'
 // import App from './App.vue'
 // import App from './Counter.vue'
 // import App from './Borders.vue'
+import App from './SelectDemo.vue'
 
 createApp(App, {
   // swapScreens: true,
diff --git a/packages/vite-plugin-vue-termui/src/index.ts b/packages/vite-plugin-vue-termui/src/index.ts
index 35dce1b..da20ecc 100644
--- a/packages/vite-plugin-vue-termui/src/index.ts
+++ b/packages/vite-plugin-vue-termui/src/index.ts
@@ -174,6 +174,9 @@ export const VueTuiComponents = new Map<string, ModuleExports>([
 
   ['transform', 'TuiTextTransform'],
   ['text-transform', 'TuiTextTransform'],
+
+  ['select', 'TuiSelect'],
+  ['option', 'TuiOption'],
 ])
 
 // copied from auto import plugin source code