diff --git a/docs/components/content/examples/ListExample.vue b/docs/components/content/examples/ListExample.vue
new file mode 100644
index 0000000000..4c49e26752
--- /dev/null
+++ b/docs/components/content/examples/ListExample.vue
@@ -0,0 +1,20 @@
+
+
+
+ {{ item.label }}
+
+
+
+
+
+
+
+
+
diff --git a/docs/components/content/examples/ListExampleGap.vue b/docs/components/content/examples/ListExampleGap.vue
new file mode 100644
index 0000000000..0ff2e1e7e5
--- /dev/null
+++ b/docs/components/content/examples/ListExampleGap.vue
@@ -0,0 +1,16 @@
+
+
+
+ {{ item.label }}
+
+
+
+
+
diff --git a/docs/components/content/examples/ListExampleItemOrientation.vue b/docs/components/content/examples/ListExampleItemOrientation.vue
new file mode 100644
index 0000000000..7b52ce5bf7
--- /dev/null
+++ b/docs/components/content/examples/ListExampleItemOrientation.vue
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+ {{ item.label }}
+
+
+
+
+
diff --git a/docs/components/content/examples/ListExampleOrdered.vue b/docs/components/content/examples/ListExampleOrdered.vue
new file mode 100644
index 0000000000..9e444db5e1
--- /dev/null
+++ b/docs/components/content/examples/ListExampleOrdered.vue
@@ -0,0 +1,20 @@
+
+
+
+ {{ item.label }}
+
+
+
+
+
+
+
+
+
diff --git a/docs/components/content/examples/ListExampleOrientation.vue b/docs/components/content/examples/ListExampleOrientation.vue
new file mode 100644
index 0000000000..7501104625
--- /dev/null
+++ b/docs/components/content/examples/ListExampleOrientation.vue
@@ -0,0 +1,22 @@
+
+
+
+ {{ item.label }}
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/components/content/examples/ListExamplePaddingClass.vue b/docs/components/content/examples/ListExamplePaddingClass.vue
new file mode 100644
index 0000000000..8947f6cbf4
--- /dev/null
+++ b/docs/components/content/examples/ListExamplePaddingClass.vue
@@ -0,0 +1,16 @@
+
+
+
+ {{ item.label }}
+
+
+
+
+
diff --git a/docs/components/content/examples/ListExampleSlotSeparatorAfter.vue b/docs/components/content/examples/ListExampleSlotSeparatorAfter.vue
new file mode 100644
index 0000000000..edfb873e2f
--- /dev/null
+++ b/docs/components/content/examples/ListExampleSlotSeparatorAfter.vue
@@ -0,0 +1,20 @@
+
+
+
+ {{ item.label }}
+
+
+
+
+
+
+
+
+
diff --git a/docs/components/content/examples/ListExampleSlotSeparatorBefore.vue b/docs/components/content/examples/ListExampleSlotSeparatorBefore.vue
new file mode 100644
index 0000000000..70eb50396e
--- /dev/null
+++ b/docs/components/content/examples/ListExampleSlotSeparatorBefore.vue
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+ {{ item.label }}
+
+
+
+
+
diff --git a/docs/components/content/examples/ListExampleWrap.vue b/docs/components/content/examples/ListExampleWrap.vue
new file mode 100644
index 0000000000..2a99ec5c2a
--- /dev/null
+++ b/docs/components/content/examples/ListExampleWrap.vue
@@ -0,0 +1,22 @@
+
+
+
+
+ {{ item.label }}
+
+
+
+
+
+
diff --git a/docs/content/7.layout/5.list.md b/docs/content/7.layout/5.list.md
new file mode 100644
index 0000000000..d7e95cf32c
--- /dev/null
+++ b/docs/content/7.layout/5.list.md
@@ -0,0 +1,87 @@
+---
+description: Create horizontal o vertical lists with separators.
+links:
+ - label: GitHub
+ icon: i-simple-icons-github
+ to: https://github.com/nuxt/ui/blob/dev/src/runtime/components/layout/List.vue
+---
+
+## Usage
+
+Add list items to the `UList` component, and a separator using the [`separator-before`](#separator-before) or [`separator-after`](#separator-after) slots.
+
+List items can be from simple HTML elements, to a `v-for` loop of components.
+
+:component-example{component="list-example"}
+
+Lists are unordered by default, created using the `ul` tag. You may change the HTML element to `ol`, which semantically makes the list _ordered_, by setting the `ordered` prop to `true`.
+
+:component-example{component="list-example-ordered"}
+
+::callout{icon="i-heroicons-light-bulb"}
+Changing `ordered` to `true` makes only a semantic change, not a visual change.
+::
+
+### Orientation
+
+By default, items are stacked vertically. To stack the items horizontally, set the `orientation` prop to `horizontal`.
+
+:component-example{component="list-example-orientation"}
+
+### Item Orientation
+
+Both list and separators have the same stacking orientation. By default, as the list is vertical, and each item (contents and separators) are also stacked vertically.
+
+The `itemOrientation` allows to change the stacking orientation of the items regardless of the list orientation.
+
+:component-example{component="list-example-item-orientation"}
+
+### Gap
+
+To add a default space in between each item, use the `gap` prop as `true`.
+
+:component-example{component="list-example-gap"}
+
+Alternatively, you may add a gap manually using the `class` attribute like any other HTML Element.
+
+:component-example{component="list-example-padding-class"}
+
+::callout{icon="i-heroicons-light-bulb"}
+Items are listed using Tailwind CSS [`flex`](https://tailwindcss.com/docs/flex). You can change this through the [UI configuration](#config).
+::
+
+### Wrapping
+
+When the items exceed the container height or width, these will not be wrapped into another line. To enable this behaviour, set the `wrap` prop to `true`.
+
+:component-example{component="list-example-wrap"}
+
+## Slots
+
+### `separator-before`
+
+Use this slot to set a separator **before** the item **contents**. It receives the current `index` of the item where is located, and both `isFirst` and `isLast` boolean if is the first or last item of the list, respectively.
+
+:component-example{component="list-example-slot-separator-before"}
+
+::callout{icon="i-heroicons-exclamation-triangle"}
+Both `isFirst` and `isLast` booleans always returns `true` if there is only one item.
+::
+
+### `separator-after`
+
+Use this slot to set a separator **after** the item **contents**. It receives the current `index` of the item where is located, and both `isFirst` and `isLast` boolean if is the first or last item of the list, respectively.
+
+:component-example{component="list-example-slot-separator-after"}
+
+::callout{icon="i-heroicons-exclamation-triangle"}
+Both `isFirst` and `isLast` booleans always returns `true` if there is only one item.
+::
+
+## Props
+
+:component-props
+
+## Config
+
+:component-preset
diff --git a/src/runtime/components/layout/List.ts b/src/runtime/components/layout/List.ts
new file mode 100644
index 0000000000..132f15b4eb
--- /dev/null
+++ b/src/runtime/components/layout/List.ts
@@ -0,0 +1,107 @@
+import { h, computed, toRef, defineComponent, onMounted } from 'vue'
+import type { PropType, SlotsType } from 'vue'
+import type { RequireAtLeastOne } from 'type-fest'
+import { twMerge, twJoin } from 'tailwind-merge'
+import { useUI } from '../../composables/useUI'
+import type { Strategy } from '../../types'
+// @ts-expect-error
+import appConfig from '#build/app.config'
+import { mergeConfig, getSlotsChildren } from '#ui/utils'
+import { list } from '#ui/ui.config'
+
+const config = mergeConfig(appConfig.ui.strategy, appConfig.ui.list, list)
+
+export default defineComponent({
+ inheritAttrs: false,
+ props: {
+ ordered: {
+ type: Boolean,
+ default: false
+ },
+ orientation: {
+ type: String as PropType<'horizontal' | 'vertical'>,
+ default: 'vertical',
+ validator (value: string) {
+ return Object.keys(config).includes(value)
+ }
+ },
+ gap: {
+ type: Boolean,
+ default: false
+ },
+ wrap: {
+ type: Boolean,
+ default: () => config.wrapItems
+ },
+ itemOrientation: {
+ type: String as PropType<'horizontal' | 'vertical'>,
+ default: undefined,
+ validator (value: string|undefined) {
+ return typeof value === 'string' ? Object.keys(config).includes(value) : true
+ }
+ },
+ class: {
+ type: [String, Object, Array] as PropType,
+ default: undefined
+ },
+ ui: {
+ type: Object as PropType>,
+ default: undefined
+ }
+ },
+ slots: Object as SlotsType<
+ RequireAtLeastOne<{
+ default: undefined,
+ 'separator-before'?: { index: number, isFirst: boolean, isLast: boolean },
+ 'separator-after'?: { index: number, isFirst: boolean, isLast: boolean },
+ }, 'separator-before' | 'separator-after'>
+ >,
+ setup (props, { slots }) {
+ const { ui, attrs } = useUI('list', toRef(props, 'ui'), config)
+
+ const listClass = computed(() => {
+ return twMerge(twJoin(
+ ui.value[props.orientation].base,
+ props.wrap ? ui.value.wrap : ui.value.nowrap,
+ props.gap ? ui.value[props.orientation].gap : ''
+ ), props.class)
+ })
+
+ const itemClass = computed(() => {
+ return twJoin(
+ ui.value[props.itemOrientation ?? props.orientation].base,
+ ui.value.nowrap
+ )
+ })
+
+ function addSeparator (array) {
+ return array.map((item, index) => {
+ const children = []
+
+ const isFirst = array.length === 1
+ || ((slots['separator-before'] && index === 1) || (slots['separator-after'] && index === 0))
+
+ const isLast = array.length === 1
+ || ((slots['separator-before'] && index === (array.length - 1)) || (slots['separator-after'] && index === (array.length - 2)))
+
+ if (slots['separator-before'] && (index > 0)) {
+ children.push(slots['separator-before']({ index, isFirst, isLast }))
+ }
+
+ children.push(item)
+
+ if (slots['separator-after'] && (index < (array.length - 1))) {
+ children.push(slots['separator-after']({ index, isFirst, isLast }))
+ }
+
+ return h('li', { class: itemClass.value }, children)
+ })
+ }
+
+ const children = computed(() => addSeparator(getSlotsChildren(slots)))
+
+ const orderedElement = computed(() => props.ordered ? 'ol' : 'ul')
+
+ return () => h(orderedElement.value, { ...attrs, class: listClass.value, ref: 'list' }, children.value)
+ }
+})
diff --git a/src/runtime/ui.config.ts b/src/runtime/ui.config.ts
index 6dc2781428..748ce7150c 100644
--- a/src/runtime/ui.config.ts
+++ b/src/runtime/ui.config.ts
@@ -996,6 +996,20 @@ export const divider = {
label: 'text-sm'
}
+export const list = {
+ vertical: {
+ base: 'flex flex-col',
+ gap: 'gap-4'
+ },
+ horizontal: {
+ base: 'flex flex-row',
+ gap: 'gap-4'
+ },
+ wrapItems: false,
+ wrap: 'flex-wrap',
+ nowrap: 'flex-nowrap'
+}
+
// Navigation
export const verticalNavigation = {