diff --git a/docs/components/content/ComponentCard.vue b/docs/components/content/ComponentCard.vue
index 4ba5f5b..3987066 100644
--- a/docs/components/content/ComponentCard.vue
+++ b/docs/components/content/ComponentCard.vue
@@ -7,7 +7,7 @@
           v-if="prop.type === 'boolean'"
           v-model="componentProps[prop.name]"
           :name="`prop-${prop.name}`"
-          variant="none"
+          tabindex="-1"
           :ui="{ wrapper: 'relative flex items-start justify-center' }"
         />
         <USelectMenu
@@ -18,6 +18,7 @@
           variant="none"
           :ui="{ width: 'w-32 !-mt-px', rounded: 'rounded-b-md', wrapper: 'relative inline-flex' }"
           class="!py-0"
+          tabindex="-1"
           :popper="{ strategy: 'fixed', placement: 'bottom-start' }"
         />
         <UInput
@@ -28,6 +29,7 @@
           variant="none"
           autocomplete="off"
           class="!py-0"
+          tabindex="-1"
           @update:model-value="val => componentProps[prop.name] = prop.type === 'number' ? Number(val) : val"
         />
       </div>
diff --git a/docs/components/content/examples/CheckboxExample.vue b/docs/components/content/examples/CheckboxExample.vue
index d34d412..4196909 100644
--- a/docs/components/content/examples/CheckboxExample.vue
+++ b/docs/components/content/examples/CheckboxExample.vue
@@ -1,5 +1,5 @@
 <script setup>
-const selected = ref(false)
+const selected = ref(true)
 </script>
 
 <template>
diff --git a/docs/components/content/examples/RangeExample.vue b/docs/components/content/examples/RangeExample.vue
new file mode 100644
index 0000000..a5aeda2
--- /dev/null
+++ b/docs/components/content/examples/RangeExample.vue
@@ -0,0 +1,7 @@
+<script setup>
+const value = ref(50)
+</script>
+
+<template>
+  <URange v-model="value" />
+</template>
diff --git a/docs/content/1.getting-started/3.theming.md b/docs/content/1.getting-started/3.theming.md
index 512ecf3..a5aaba3 100644
--- a/docs/content/1.getting-started/3.theming.md
+++ b/docs/content/1.getting-started/3.theming.md
@@ -33,7 +33,7 @@ Likewise, you can't define a `primary` color in your `tailwind.config.ts` as it
 We'd advise you to use those colors in your components and pages, e.g. `text-primary-500 dark:text-primary-400`, `bg-gray-100 dark:bg-gray-900`, etc. so your app automatically adapts when changing your `app.config.ts`.
 ::
 
-Components having a `color` prop like [Avatar](/elements/avatar#chip), [Badge](/elements/badge#style), [Button](/elements/button#style), [Input](/elements/input#style) (inherited in [Select](/forms/select) and [SelectMenu](/forms/select-menu)) and [Notification](/overlays/notification#timeout) will use the `primary` color by default but will handle all the colors defined in your `tailwind.config.ts` or the default Tailwind CSS colors.
+Components having a `color` prop like [Avatar](/elements/avatar#chip), [Badge](/elements/badge#style), [Button](/elements/button#style), [Input](/elements/input#style) (inherited in [Select](/forms/select) and [SelectMenu](/forms/select-menu)), [Radio](/forms/radio), [Checkbox](/forms/checkbox), [Toggle](/forms/toggle), [Range](/forms/range) and [Notification](/overlays/notification#timeout) will use the `primary` color by default but will handle all the colors defined in your `tailwind.config.ts` or the default Tailwind CSS colors.
 
 Variant classes of those components are defined with a syntax like `bg-{color}-500 dark:bg-{color}-400` so they can be used with any color. However, this means that Tailwind will not find those classes and therefore will not generate the corresponding CSS.
 
diff --git a/docs/content/3.forms/5.checkbox.md b/docs/content/3.forms/5.checkbox.md
index 9c1c634..c34d5fb 100644
--- a/docs/content/3.forms/5.checkbox.md
+++ b/docs/content/3.forms/5.checkbox.md
@@ -14,7 +14,7 @@ Use a `v-model` to make the Checkbox reactive.
 #code
 ```vue
 <script setup>
-const selected = ref(false)
+const selected = ref(true)
 </script>
 
 <template>
@@ -36,6 +36,20 @@ props:
 ---
 ::
 
+### Style
+
+Use the `color` prop to change the style of the Checkbox.
+
+::component-card
+---
+baseProps:
+  name: 'checkbox2'
+  label: 'Label'
+props:
+  color: 'primary'
+---
+::
+
 ### Required
 
 Use the `required` prop to display a red star next to the label.
@@ -43,7 +57,7 @@ Use the `required` prop to display a red star next to the label.
 ::component-card
 ---
 baseProps:
-  name: 'checkbox2'
+  name: 'checkbox3'
 props:
   label: 'Label'
   required: true
@@ -57,7 +71,7 @@ Use the `help` prop to display some text under the Checkbox.
 ::component-card
 ---
 baseProps:
-  name: 'checkbox3'
+  name: 'checkbox4'
 props:
   label: 'Label'
   help: 'Please check this box'
diff --git a/docs/content/3.forms/6.radio.md b/docs/content/3.forms/6.radio.md
index 3ef937d..d806080 100644
--- a/docs/content/3.forms/6.radio.md
+++ b/docs/content/3.forms/6.radio.md
@@ -50,6 +50,20 @@ props:
 ---
 ::
 
+### Style
+
+Use the `color` prop to change the style of the Radio.
+
+::component-card
+---
+baseProps:
+  name: 'radio2'
+  label: 'Label'
+props:
+  color: 'primary'
+---
+::
+
 ### Required
 
 Use the `required` prop to display a red star next to the label.
@@ -57,7 +71,7 @@ Use the `required` prop to display a red star next to the label.
 ::component-card
 ---
 baseProps:
-  name: 'radio2'
+  name: 'radio3'
 props:
   label: 'Label'
   required: true
@@ -71,7 +85,7 @@ Use the `help` prop to display some text under the Radio.
 ::component-card
 ---
 baseProps:
-  name: 'radio3'
+  name: 'radio4'
 props:
   label: 'Label'
   help: 'Please choose one'
@@ -85,7 +99,7 @@ Use the `disabled` prop to disable the Radio.
 ::component-card
 ---
 baseProps:
-  name: 'radio4'
+  name: 'radio5'
   value: true
 props:
   disabled: true
diff --git a/docs/content/3.forms/7.toggle.md b/docs/content/3.forms/7.toggle.md
index 8ede6d5..3994d64 100644
--- a/docs/content/3.forms/7.toggle.md
+++ b/docs/content/3.forms/7.toggle.md
@@ -26,6 +26,17 @@ const selected = ref(false)
 ```
 ::
 
+### Style
+
+Use the `color` prop to change the style of the Toggle.
+
+::component-card
+---
+props:
+  color: 'primary'
+---
+::
+
 ### Icon
 
 Use any icon from [Iconify](https://icones.js.org) by setting the `on-icon` and `off-icon` props by using this pattern: `i-{collection_name}-{icon_name}` or change it globally in `ui.toggle.default.onIcon` and `ui.toggle.default.offIcon`.
diff --git a/docs/content/3.forms/8.range.md b/docs/content/3.forms/8.range.md
new file mode 100644
index 0000000..1bf519e
--- /dev/null
+++ b/docs/content/3.forms/8.range.md
@@ -0,0 +1,101 @@
+---
+github: true
+description: Display a range field
+navigation:
+  badge: "Edge"
+---
+
+## Usage
+
+Use a `v-model` to make the Range reactive.
+
+::component-example
+#default
+:range-example
+
+#code
+```vue
+<script setup>
+const value = ref(50)
+</script>
+
+<template>
+  <URange v-model="value" />
+</template>
+```
+::
+
+### Style
+
+Use the `color` prop to change the visual style of the Range.
+
+::component-card
+---
+baseProps:
+  name: range'
+  placeholder: 'Search...'
+props:
+  color: 'primary'
+---
+::
+
+### Size
+
+Use the `size` prop to change the size of the Range.
+
+::component-card
+---
+baseProps:
+  name: 'range'
+props:
+  size: 'md'
+---
+::
+
+### Disabled
+
+Use the `disabled` prop to disable the Range.
+
+::component-card
+---
+baseProps:
+  name: 'range'
+props:
+  disabled: true
+---
+::
+
+### Min and Max
+
+Use the `min` and `max` prop to configure the Range.
+
+::component-card
+---
+baseProps:
+  name: 'range'
+props:
+  min: 0
+  max: 100
+---
+::
+
+### Step
+
+Use the `step` prop to change the step increment.
+
+::component-card
+---
+baseProps:
+  name: 'range'
+props:
+  step: 20
+---
+::
+
+## Props
+
+:component-props
+
+## Preset
+
+:component-preset
diff --git a/docs/content/3.forms/9.form-group.md b/docs/content/3.forms/9.form-group.md
new file mode 100644
index 0000000..2ffabb9
--- /dev/null
+++ b/docs/content/3.forms/9.form-group.md
@@ -0,0 +1,141 @@
+---
+github: 
+  suffix: .ts
+description: Display a label and additional informations around a form element.
+---
+
+
+## Usage
+
+Use the FormGroup component around an [Input](/forms/input), [Textarea](/forms/textarea), [Select](/forms/select) or a [SelectMenu](/forms/select-menu) with the `name` prop to automatically associate a `<label>` element with the form element.
+
+::component-card
+---
+props:
+  name: 'email'
+  label: 'Email'
+code: >-
+
+  <UInput placeholder="you@example.com" icon="i-heroicons-envelope" />
+---
+
+#default
+:u-input{placeholder="you@example.com" icon="i-heroicons-envelope"}
+::
+
+### Required
+
+Use the `required` prop to indicate that the form element is required.
+
+::component-card
+---
+baseProps:
+  name: 'group-required'
+props:
+  label: 'Email'
+  required: true
+code: >-
+
+  <UInput placeholder="you@example.com" icon="i-heroicons-envelope" />
+---
+
+#default
+:u-input{placeholder="you@example.com" icon="i-heroicons-envelope"}
+::
+
+### Description
+
+Use the `description` prop to display a description below the label.
+
+::component-card
+---
+baseProps:
+  name: 'group-description'
+props:
+  label: 'Email'
+  description: "We'll only use this for spam."
+code: >-
+
+  <UInput placeholder="you@example.com" icon="i-heroicons-envelope" />
+---
+
+#default
+:u-input{placeholder="you@example.com" icon="i-heroicons-envelope"}
+::
+
+### Hint
+
+Use the `hint` prop to display a hint above the form element.
+
+::component-card
+---
+baseProps:
+  name: 'group-hint'
+props:
+  label: 'Email'
+  hint: 'Optional'
+code: >-
+
+  <UInput placeholder="you@example.com" icon="i-heroicons-envelope" />
+---
+
+#default
+:u-input{placeholder="you@example.com" icon="i-heroicons-envelope"}
+::
+
+### Help
+
+Use the `help` prop to display an help message below the form element.
+
+::component-card
+---
+baseProps:
+  name: 'group-help'
+props:
+  label: 'Email'
+  help: 'We will never share your email with anyone else.'
+code: >-
+
+  <UInput placeholder="you@example.com" icon="i-heroicons-envelope" />
+---
+
+#default
+:u-input{placeholder="you@example.com" icon="i-heroicons-envelope"}
+::
+
+### Error
+
+Use the `error` prop to display an error message below the form element.
+
+When used together with the `help` prop, the `error` prop will take precedence.
+
+::component-card
+---
+baseProps:
+  name: 'group-error'
+props:
+  label: 'Email'
+  help: 'We will never share your email with anyone else.'
+  error: "Not a valid email address."
+code: >-
+
+  <UInput placeholder="you@example.com" trailing-icon="i-heroicons-exclamation-triangle-20-solid" />
+---
+
+#default
+:u-input{model-value="acidjazz" placeholder="you@example.com" trailing-icon="i-heroicons-exclamation-triangle-20-solid"}
+::
+
+You can also use the `error` prop as a boolean to mark the form element as invalid.
+
+::alert{icon="i-heroicons-light-bulb"}
+  The `error` prop will automatically set the `color` prop of the form element to `red`.
+::
+
+## Props
+
+:component-props
+
+## Preset
+
+:component-preset
diff --git a/docs/content/6.overlays/6.notification.md b/docs/content/6.overlays/6.notification.md
index d0a37af..65f017e 100644
--- a/docs/content/6.overlays/6.notification.md
+++ b/docs/content/6.overlays/6.notification.md
@@ -158,7 +158,7 @@ props:
 ---
 ::
 
-### Color
+### Style
 
 Use the `color` prop to change the progress and icon color of the Notification.
 
diff --git a/docs/plugins/ui.ts b/docs/plugins/ui.ts
index 45e82f3..c77c5c6 100644
--- a/docs/plugins/ui.ts
+++ b/docs/plugins/ui.ts
@@ -8,8 +8,8 @@ export default defineNuxtPlugin({
     const appConfig = useAppConfig()
 
     const root = computed(() => {
-      const primary = colors[appConfig.ui.primary]
-      const gray = colors[appConfig.ui.gray]
+      const primary: Record<string, string> | undefined = colors[appConfig.ui.primary]
+      const gray: Record<string, string> | undefined = colors[appConfig.ui.gray]
 
       return `:root {
         ${Object.entries(primary || colors.green).map(([key, value]) => `--color-primary-${key}: ${hexToRgb(value)};`).join('\n')}
diff --git a/playground/app.config.ts b/playground/app.config.ts
new file mode 100644
index 0000000..e2a0c6c
--- /dev/null
+++ b/playground/app.config.ts
@@ -0,0 +1,6 @@
+export default defineAppConfig({
+    ui: {
+        primary: 'indigo',
+        gray: 'slate',
+    }
+})
diff --git a/playground/app.vue b/playground/app.vue
index 15acd15..ad8a890 100644
--- a/playground/app.vue
+++ b/playground/app.vue
@@ -1,6 +1,6 @@
 <template>
   <div w-screen h-screen bg-gray-900 flex items-center justify-center text-white>
-    <u-button color="black" variant="outline">
+    <u-button>
       hello there
     </u-button>
   </div>
diff --git a/src/module.ts b/src/module.ts
index 57324fb..8e84643 100644
--- a/src/module.ts
+++ b/src/module.ts
@@ -66,8 +66,8 @@ export default defineNuxtModule<ModuleOptions>({
     })
 
     const preset = presetUno()
-
     const globalColors:any = preset.theme?.colors
+
     globalColors.primary = {
       50: 'rgb(var(--color-primary-50) / <alpha-value>)',
       100: 'rgb(var(--color-primary-100) / <alpha-value>)',
@@ -106,11 +106,12 @@ export default defineNuxtModule<ModuleOptions>({
         'truegray', 'true-gray', 'coolgray', 'cool-gray','bluegray', 'blue-gray'
       ].includes(color) )
 
+
     nuxt.options.appConfig.ui = {
       ...nuxt.options.appConfig.ui,
       primary: 'green',
       gray: 'slate',
-      colors: colors,
+      colors,
     }
 
     if (preset.theme?.colors) {
diff --git a/src/runtime/app.config.ts b/src/runtime/app.config.ts
index 197487b..c5f6e3e 100644
--- a/src/runtime/app.config.ts
+++ b/src/runtime/app.config.ts
@@ -327,10 +327,10 @@ const input = {
   },
   color: {
     white: {
-      outline: 'shadow-sm bg-white dark:bg-gray-900 text-gray-900 dark:text-white ring-1 ring-inset ring-gray-300 dark:ring-gray-700 focus:ring-2 focus:ring-primary-500 dark:focus:ring-primary-400',
+      outline: 'shadow-sm bg-white dark:bg-gray-900 text-gray-900 dark:text-white ring-1 ring-inset ring-gray-300 dark:ring-gray-700 focus:ring-2 focus:ring-primary-500 dark:focus:ring-primary-400'
     },
     gray: {
-      outline: 'shadow-sm bg-gray-50 dark:bg-gray-800 text-gray-900 dark:text-white ring-1 ring-inset ring-gray-300 dark:ring-gray-700 focus:ring-2 focus:ring-primary-500 dark:focus:ring-primary-400',
+      outline: 'shadow-sm bg-gray-50 dark:bg-gray-800 text-gray-900 dark:text-white ring-1 ring-inset ring-gray-300 dark:ring-gray-700 focus:ring-2 focus:ring-primary-500 dark:focus:ring-primary-400'
     }
   },
   variant: {
@@ -400,7 +400,7 @@ const textarea = {
   default: {
     size: 'sm',
     color: 'white',
-    variant: 'outline',
+    variant: 'outline'
   }
 }
 
@@ -473,24 +473,40 @@ const selectMenu = {
 
 const radio = {
   wrapper: 'relative flex items-start',
-  base: 'h-4 w-4 text-primary-500 dark:text-primary-400 focus-visible:ring-2 focus-visible:ring-offset-2 bg-white dark:bg-gray-900 dark:checked:bg-current dark:checked:border-transparent focus-visible:ring-primary-500 dark:focus-visible:ring-primary-400 focus-visible:ring-offset-white dark:focus-visible:ring-offset-gray-900 border-gray-300 dark:border-gray-700 disabled:opacity-50 disabled:cursor-not-allowed focus:ring-0 focus:ring-transparent focus:ring-offset-transparent',
+  base: 'h-4 w-4 dark:checked:bg-current dark:checked:border-transparent disabled:opacity-50 disabled:cursor-not-allowed focus:ring-0 focus:ring-transparent focus:ring-offset-transparent',
+  color: 'text-{color}-500 dark:text-{color}-400',
+  background: 'bg-white dark:bg-gray-900',
+  border: 'border border-gray-300 dark:border-gray-700',
+  ring: 'focus-visible:ring-2 focus-visible:ring-{color}-500 dark:focus-visible:ring-{color}-400 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:focus-visible:ring-offset-gray-900',
   label: 'font-medium text-gray-700 dark:text-gray-200',
   required: 'text-red-500 dark:text-red-400',
-  help: 'text-gray-500 dark:text-gray-400'
+  help: 'text-gray-500 dark:text-gray-400',
+  default: {
+    color: 'primary'
+  }
 }
 
 const checkbox = {
   wrapper: 'relative flex items-start',
-  base: 'h-4 w-4 text-primary-500 dark:text-primary-400 focus-visible:ring-2 focus-visible:ring-offset-2 bg-white dark:bg-gray-900 dark:checked:bg-current dark:checked:border-transparent dark:indeterminate:bg-current dark:indeterminate:border-transparent focus-visible:ring-primary-500 dark:focus-visible:ring-primary-400 focus-visible:ring-offset-white dark:focus-visible:ring-offset-gray-900 border-gray-300 dark:border-gray-700 disabled:opacity-50 disabled:cursor-not-allowed focus:ring-0 focus:ring-transparent focus:ring-offset-transparent',
+  base: 'h-4 w-4 dark:checked:bg-current dark:checked:border-transparent dark:indeterminate:bg-current dark:indeterminate:border-transparent disabled:opacity-50 disabled:cursor-not-allowed focus:ring-0 focus:ring-transparent focus:ring-offset-transparent',
   rounded: 'rounded',
+  color: 'text-{color}-500 dark:text-{color}-400',
+  background: 'bg-white dark:bg-gray-900',
+  border: 'border border-gray-300 dark:border-gray-700',
+  ring: 'focus-visible:ring-2 focus-visible:ring-{color}-500 dark:focus-visible:ring-{color}-400 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:focus-visible:ring-offset-gray-900',
   label: 'font-medium text-gray-700 dark:text-gray-200',
   required: 'text-red-500 dark:text-red-400',
-  help: 'text-gray-500 dark:text-gray-400'
+  help: 'text-gray-500 dark:text-gray-400',
+  default: {
+    color: 'primary'
+  }
 }
 
 const toggle = {
-  base: 'relative inline-flex flex-shrink-0 h-5 w-9 border-2 border-transparent rounded-full cursor-pointer disabled:cursor-not-allowed focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-500 dark:focus-visible:ring-primary-400 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:focus-visible:ring-offset-gray-900',
-  active: 'bg-primary-500 dark:bg-primary-400',
+  base: 'relative inline-flex h-5 w-9 flex-shrink-0 border-2 border-transparent disabled:cursor-not-allowed disabled:opacity-50 focus:outline-none',
+  rounded: 'rounded-full',
+  ring: 'focus-visible:ring-2 focus-visible:ring-{color}-500 dark:focus-visible:ring-{color}-400 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:focus-visible:ring-offset-gray-900',
+  active: 'bg-{color}-500 dark:bg-{color}-400',
   inactive: 'bg-gray-200 dark:bg-gray-700',
   container: {
     base: 'pointer-events-none relative inline-block h-4 w-4 rounded-full bg-white dark:bg-gray-900 shadow transform ring-0 transition ease-in-out duration-200',
@@ -501,12 +517,46 @@ const toggle = {
     base: 'absolute inset-0 h-full w-full flex items-center justify-center transition-opacity',
     active: 'opacity-100 ease-in duration-200',
     inactive: 'opacity-0 ease-out duration-100',
-    on: 'h-3 w-3 text-primary-500 dark:text-primary-400',
+    on: 'h-3 w-3 text-{color}-500 dark:text-{color}-400',
     off: 'h-3 w-3 text-gray-400 dark:text-gray-500'
   },
   default: {
     onIcon: null,
-    offIcon: null
+    offIcon: null,
+    color: 'primary'
+  }
+}
+
+const range = {
+  wrapper: 'relative w-full',
+  base: 'w-full absolute appearance-none cursor-pointer disabled:cursor-not-allowed disabled:opacity-50 focus:outline-none [&::-webkit-slider-runnable-track]:h-full [&::-moz-slider-runnable-track]:h-full',
+  background: 'bg-gray-200 dark:bg-gray-700',
+  rounded: 'rounded-lg',
+  ring: 'focus-visible:ring-2 focus-visible:ring-{color}-500 dark:focus-visible:ring-{color}-400 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:focus-visible:ring-offset-gray-900',
+  progress: {
+    base: 'absolute inset-0 h-full pointer-events-none',
+    rounded: 'rounded-l-lg',
+    background: 'bg-{color}-500 dark:bg-{color}-400'
+  },
+  thumb: {
+    base: `[&::-webkit-slider-thumb]:relative [&::-moz-range-thumb]:relative [&::-webkit-slider-thumb]:z-[1] [&::-moz-range-thumb]:z-[1] [&::-webkit-slider-thumb]:appearance-none [&::-moz-range-thumb]:appearance-none [&::-webkit-slider-thumb]:rounded-full [&::-moz-range-thumb]:rounded-full [&::-moz-range-thumb]:border-0`,
+    color: 'text-{color}-500 dark:text-{color}-400',
+    background: '[&::-webkit-slider-thumb]:bg-white [&::-webkit-slider-thumb]:dark:bg-gray-900 [&::-moz-range-thumb]:bg-current',
+    ring: '[&::-webkit-slider-thumb]:ring-2 [&::-webkit-slider-thumb]:ring-current',
+    size: {
+      sm: '[&::-webkit-slider-thumb]:h-3 [&::-moz-range-thumb]:h-3 [&::-webkit-slider-thumb]:w-3 [&::-moz-range-thumb]:w-3 [&::-webkit-slider-thumb]:-mt-1 [&::-moz-range-thumb]:-mt-1',
+      md: '[&::-webkit-slider-thumb]:h-4 [&::-moz-range-thumb]:h-4 [&::-webkit-slider-thumb]:w-4 [&::-moz-range-thumb]:w-4 [&::-webkit-slider-thumb]:-mt-1 [&::-moz-range-thumb]:-mt-1',
+      lg: '[&::-webkit-slider-thumb]:h-5 [&::-moz-range-thumb]:h-5 [&::-webkit-slider-thumb]:w-5 [&::-moz-range-thumb]:w-5 [&::-webkit-slider-thumb]:-mt-1 [&::-moz-range-thumb]:-mt-1'
+    }
+  },
+  size: {
+    sm: 'h-1',
+    md: 'h-2',
+    lg: 'h-3'
+  },
+  default: {
+    size: 'md',
+    color: 'primary'
   }
 }
 
@@ -872,6 +922,7 @@ export default {
     checkbox,
     radio,
     toggle,
+    range,
     card,
     container,
     skeleton,
diff --git a/src/runtime/components/data/Table.vue b/src/runtime/components/data/Table.vue
index 4e76385..b768831 100644
--- a/src/runtime/components/data/Table.vue
+++ b/src/runtime/components/data/Table.vue
@@ -77,7 +77,7 @@ import appConfig from '#build/app.config'
 
 // const appConfig = useAppConfig()
 
-function defaultComparator<T>(a: T, z: T): boolean {
+function defaultComparator<T> (a: T, z: T): boolean {
   return a === z
 }
 
diff --git a/src/runtime/components/forms/Checkbox.vue b/src/runtime/components/forms/Checkbox.vue
index 2b20be0..292b6eb 100644
--- a/src/runtime/components/forms/Checkbox.vue
+++ b/src/runtime/components/forms/Checkbox.vue
@@ -3,7 +3,7 @@
     <div class="flex items-center h-5">
       <input
         :id="name"
-        v-model="isChecked"
+        v-model="toggle"
         :name="name"
         :required="required"
         :value="value"
@@ -12,10 +12,8 @@
         :indeterminate="indeterminate"
         type="checkbox"
         class="form-checkbox"
-        :class="[ui.base, ui.rounded, ui.custom]"
+        :class="inputClass"
         v-bind="$attrs"
-        @focus="$emit('focus', $event)"
-        @blur="$emit('blur', $event)"
       >
     </div>
     <div v-if="label || $slots.label" class="ml-3 text-sm">
@@ -34,6 +32,7 @@
 import { computed, defineComponent } from 'vue'
 import type { PropType } from 'vue'
 import { defu } from 'defu'
+import { classNames } from '../../utils'
 import { useAppConfig } from '#imports'
 // TODO: Remove
 // @ts-expect-error
@@ -80,19 +79,26 @@ export default defineComponent({
       type: Boolean,
       default: false
     },
+    color: {
+      type: String,
+      default: () => appConfig.ui.checkbox.default.color,
+      validator (value: string) {
+        return appConfig.ui.colors.includes(value)
+      }
+    },
     ui: {
       type: Object as PropType<Partial<typeof appConfig.ui.checkbox>>,
       default: () => appConfig.ui.checkbox
     }
   },
-  emits: ['update:modelValue', 'focus', 'blur'],
+  emits: ['update:modelValue'],
   setup (props, { emit }) {
     // TODO: Remove
     const appConfig = useAppConfig()
 
     const ui = computed<Partial<typeof appConfig.ui.checkbox>>(() => defu({}, props.ui, appConfig.ui.checkbox))
 
-    const isChecked = computed({
+    const toggle = computed({
       get () {
         return props.modelValue
       },
@@ -101,10 +107,22 @@ export default defineComponent({
       }
     })
 
+    const inputClass = computed(() => {
+      return classNames(
+        ui.value.base,
+        ui.value.rounded,
+        ui.value.background,
+        ui.value.border,
+        ui.value.ring.replaceAll('{color}', props.color),
+        ui.value.color.replaceAll('{color}', props.color)
+      )
+    })
+
     return {
       // eslint-disable-next-line vue/no-dupe-keys
       ui,
-      isChecked
+      toggle,
+      inputClass
     }
   }
 })
diff --git a/src/runtime/components/forms/Input.vue b/src/runtime/components/forms/Input.vue
index c597a42..c992cde 100644
--- a/src/runtime/components/forms/Input.vue
+++ b/src/runtime/components/forms/Input.vue
@@ -13,8 +13,6 @@
       :class="inputClass"
       v-bind="$attrs"
       @input="onInput"
-      @focus="$emit('focus', $event)"
-      @blur="$emit('blur', $event)"
     >
     <slot />
 
@@ -140,7 +138,7 @@ export default defineComponent({
       default: () => appConfig.ui.input
     }
   },
-  emits: ['update:modelValue', 'focus', 'blur'],
+  emits: ['update:modelValue'],
   setup (props, { emit, slots }) {
     // TODO: Remove
     const appConfig = useAppConfig()
diff --git a/src/runtime/components/forms/Radio.vue b/src/runtime/components/forms/Radio.vue
index 8232ee2..85ed506 100644
--- a/src/runtime/components/forms/Radio.vue
+++ b/src/runtime/components/forms/Radio.vue
@@ -3,17 +3,15 @@
     <div class="flex items-center h-5">
       <input
         :id="`${name}-${value}`"
-        v-model="isChecked"
+        v-model="pick"
         :name="name"
         :required="required"
         :value="value"
         :disabled="disabled"
         type="radio"
         class="form-radio"
-        :class="[ui.base, ui.custom]"
+        :class="inputClass"
         v-bind="$attrs"
-        @focus="$emit('focus', $event)"
-        @blur="$emit('blur', $event)"
       >
     </div>
     <div v-if="label || $slots.label" class="ml-3 text-sm">
@@ -32,6 +30,7 @@
 import { computed, defineComponent } from 'vue'
 import type { PropType } from 'vue'
 import { defu } from 'defu'
+import { classNames } from '../../utils'
 import { useAppConfig } from '#imports'
 // TODO: Remove
 // @ts-expect-error
@@ -70,19 +69,26 @@ export default defineComponent({
       type: Boolean,
       default: false
     },
+    color: {
+      type: String,
+      default: () => appConfig.ui.radio.default.color,
+      validator (value: string) {
+        return appConfig.ui.colors.includes(value)
+      }
+    },
     ui: {
       type: Object as PropType<Partial<typeof appConfig.ui.radio>>,
       default: () => appConfig.ui.radio
     }
   },
-  emits: ['update:modelValue', 'focus', 'blur'],
+  emits: ['update:modelValue'],
   setup (props, { emit }) {
     // TODO: Remove
     const appConfig = useAppConfig()
 
     const ui = computed<Partial<typeof appConfig.ui.radio>>(() => defu({}, props.ui, appConfig.ui.radio))
 
-    const isChecked = computed({
+    const pick = computed({
       get () {
         return props.modelValue
       },
@@ -91,10 +97,21 @@ export default defineComponent({
       }
     })
 
+    const inputClass = computed(() => {
+      return classNames(
+        ui.value.base,
+        ui.value.background,
+        ui.value.border,
+        ui.value.ring.replaceAll('{color}', props.color),
+        ui.value.color.replaceAll('{color}', props.color)
+      )
+    })
+
     return {
       // eslint-disable-next-line vue/no-dupe-keys
       ui,
-      isChecked
+      pick,
+      inputClass
     }
   }
 })
diff --git a/src/runtime/components/forms/Range.vue b/src/runtime/components/forms/Range.vue
new file mode 100644
index 0000000..d43f220
--- /dev/null
+++ b/src/runtime/components/forms/Range.vue
@@ -0,0 +1,148 @@
+<template>
+  <div :class="wrapperClass">
+    <input
+      :id="name"
+      ref="input"
+      v-model.number="value"
+      :name="name"
+      :min="min"
+      :max="max"
+      :disabled="disabled"
+      :step="step"
+      type="range"
+      :class="[inputClass, thumbClass]"
+      v-bind="$attrs"
+    >
+
+    <span :class="progressClass" :style="progressStyle" />
+  </div>
+</template>
+
+<script lang="ts">
+import { computed, defineComponent } from 'vue'
+import type { PropType } from 'vue'
+import { defu } from 'defu'
+import { classNames } from '../../utils'
+import { useAppConfig } from '#imports'
+// TODO: Remove
+// @ts-expect-error
+import appConfig from '#build/app.config'
+
+export default defineComponent({
+  inheritAttrs: false,
+  props: {
+    modelValue: {
+      type: Number,
+      default: 0
+    },
+    name: {
+      type: String,
+      default: null
+    },
+    disabled: {
+      type: Boolean,
+      default: false
+    },
+    min: {
+      type: Number,
+      default: 0
+    },
+    max: {
+      type: Number,
+      default: 100
+    },
+    step: {
+      type: Number,
+      default: 1
+    },
+    size: {
+      type: String,
+      default: () => appConfig.ui.range.default.size,
+      validator (value: string) {
+        return Object.keys(appConfig.ui.range.size).includes(value)
+      }
+    },
+    color: {
+      type: String,
+      default: () => appConfig.ui.range.default.color,
+      validator (value: string) {
+        return appConfig.ui.colors.includes(value)
+      }
+    },
+    ui: {
+      type: Object as PropType<Partial<typeof appConfig.ui.range>>,
+      default: () => appConfig.ui.range
+    }
+  },
+  emits: ['update:modelValue'],
+  setup (props, { emit }) {
+    // TODO: Remove
+    const appConfig = useAppConfig()
+
+    const ui = computed<Partial<typeof appConfig.ui.range>>(() => defu({}, props.ui, appConfig.ui.range))
+
+    const value = computed({
+      get () {
+        return props.modelValue
+      },
+      set (value) {
+        emit('update:modelValue', value)
+      }
+    })
+
+    const wrapperClass = computed(() => {
+      return classNames(
+        ui.value.wrapper,
+        ui.value.size[props.size]
+      )
+    })
+
+    const inputClass = computed(() => {
+      return classNames(
+        ui.value.base,
+        ui.value.background,
+        ui.value.rounded,
+        ui.value.ring.replaceAll('{color}', props.color),
+        ui.value.size[props.size]
+      )
+    })
+
+    const thumbClass = computed(() => {
+      return classNames(
+        ui.value.thumb.base,
+        // Intermediate class to allow thumb ring or background color (set to `current`) as it's impossible to safelist with arbitrary values
+        ui.value.thumb.color.replaceAll('{color}', props.color),
+        ui.value.thumb.ring,
+        ui.value.thumb.background,
+        ui.value.thumb.size[props.size]
+      )
+    })
+
+    const progressClass = computed(() => {
+      return classNames(
+        ui.value.progress.base,
+        ui.value.progress.rounded,
+        ui.value.progress.background.replaceAll('{color}', props.color),
+        ui.value.size[props.size]
+      )
+    })
+
+    const progressStyle = computed(() => {
+      return {
+        width: `${(props.modelValue / props.max) * 100}%`
+      }
+    })
+
+    return {
+      // eslint-disable-next-line vue/no-dupe-keys
+      ui,
+      value,
+      wrapperClass,
+      inputClass,
+      thumbClass,
+      progressClass,
+      progressStyle
+    }
+  }
+})
+</script>
diff --git a/src/runtime/components/forms/Select.vue b/src/runtime/components/forms/Select.vue
index 256f315..976095a 100644
--- a/src/runtime/components/forms/Select.vue
+++ b/src/runtime/components/forms/Select.vue
@@ -165,7 +165,7 @@ export default defineComponent({
       default: () => appConfig.ui.select
     }
   },
-  emits: ['update:modelValue', 'focus', 'blur'],
+  emits: ['update:modelValue'],
   setup (props, { emit, slots }) {
     // TODO: Remove
     const appConfig = useAppConfig()
diff --git a/src/runtime/components/forms/Textarea.vue b/src/runtime/components/forms/Textarea.vue
index 496d9b6..d5a5ca8 100644
--- a/src/runtime/components/forms/Textarea.vue
+++ b/src/runtime/components/forms/Textarea.vue
@@ -13,8 +13,6 @@
       :class="textareaClass"
       v-bind="$attrs"
       @input="onInput"
-      @focus="$emit('focus', $event)"
-      @blur="$emit('blur', $event)"
     />
   </div>
 </template>
@@ -103,7 +101,7 @@ export default defineComponent({
       default: () => appConfig.ui.textarea
     }
   },
-  emits: ['update:modelValue', 'focus', 'blur'],
+  emits: ['update:modelValue'],
   setup (props, { emit }) {
     const textarea = ref<HTMLTextAreaElement | null>(null)
 
diff --git a/src/runtime/components/forms/Toggle.vue b/src/runtime/components/forms/Toggle.vue
index 25dd59e..6403d48 100644
--- a/src/runtime/components/forms/Toggle.vue
+++ b/src/runtime/components/forms/Toggle.vue
@@ -3,14 +3,14 @@
     v-model="active"
     :name="name"
     :disabled="disabled"
-    :class="[active ? ui.active : ui.inactive, ui.base]"
+    :class="switchClass"
   >
     <span :class="[active ? ui.container.active : ui.container.inactive, ui.container.base]">
       <span v-if="onIcon" :class="[active ? ui.icon.active : ui.icon.inactive, ui.icon.base]" aria-hidden="true">
-        <UIcon :name="onIcon" :class="ui.icon.on" />
+        <UIcon :name="onIcon" :class="onIconClass" />
       </span>
       <span v-if="offIcon" :class="[active ? ui.icon.inactive : ui.icon.active, ui.icon.base]" aria-hidden="true">
-        <UIcon :name="offIcon" :class="ui.icon.off" />
+        <UIcon :name="offIcon" :class="offIconClass" />
       </span>
     </span>
   </Switch>
@@ -22,6 +22,7 @@ import type { PropType } from 'vue'
 import { defu } from 'defu'
 import { Switch } from '@headlessui/vue'
 import UIcon from '../elements/Icon.vue'
+import { classNames } from '../../utils'
 import { useAppConfig } from '#imports'
 // TODO: Remove
 // @ts-expect-error
@@ -56,6 +57,13 @@ export default defineComponent({
       type: String,
       default: () => appConfig.ui.toggle.default.offIcon
     },
+    color: {
+      type: String,
+      default: () => appConfig.ui.toggle.default.color,
+      validator (value: string) {
+        return appConfig.ui.colors.includes(value)
+      }
+    },
     ui: {
       type: Object as PropType<Partial<typeof appConfig.ui.toggle>>,
       default: () => appConfig.ui.toggle
@@ -77,10 +85,34 @@ export default defineComponent({
       }
     })
 
+    const switchClass = computed(()=>{
+      return classNames(
+        ui.value.base,
+        ui.value.rounded,
+        ui.value.ring.replaceAll('{color}', props.color),
+        (active.value ? ui.value.active : ui.value.inactive).replaceAll('{color}', props.color)
+      )
+    })
+
+    const onIconClass = computed(()=>{
+      return classNames(
+        ui.value.icon.on.replaceAll('{color}', props.color)
+      )
+    })
+
+    const offIconClass = computed(()=>{
+      return classNames(
+        ui.value.icon.off.replaceAll('{color}', props.color)
+      )
+    })
+
     return {
       // eslint-disable-next-line vue/no-dupe-keys
       ui,
-      active
+      active,
+      switchClass,
+      onIconClass,
+      offIconClass
     }
   }
 })
diff --git a/src/runtime/components/navigation/CommandPalette.vue b/src/runtime/components/navigation/CommandPalette.vue
index aa59bd6..9635250 100644
--- a/src/runtime/components/navigation/CommandPalette.vue
+++ b/src/runtime/components/navigation/CommandPalette.vue
@@ -78,8 +78,8 @@ import type { Group, Command } from '../../types/command-palette'
 import UIcon from '../elements/Icon.vue'
 import UButton from '../elements/Button.vue'
 import type { Button } from '../../types/button'
-import { classNames } from '../../utils'
 import CommandPaletteGroup from './CommandPaletteGroup.vue'
+import { classNames } from '../../utils'
 import { useAppConfig } from '#imports'
 // TODO: Remove
 // @ts-expect-error