diff --git a/.changeset/red-buttons-eat.md b/.changeset/red-buttons-eat.md
new file mode 100644
index 0000000000..7377f1209f
--- /dev/null
+++ b/.changeset/red-buttons-eat.md
@@ -0,0 +1,5 @@
+---
+"@ultraviolet/form": minor
+---
+
+Add ``
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index 6b47b59741..d4e9814c49 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -1,15 +1,15 @@
-* @matthprost @lisalupi
-/packages/form @johnrazeur
-**/package.json @scaleway/front-kernel
-Dockerfile @scaleway/front-kernel
-babel.config.json @scaleway/front-kernel
-tsconfig.json @scaleway/front-kernel
-vite.config.ts @scaleway/front-kernel
-turbo.json @scaleway/front-kernel
-biome.json @scaleway/front-kernel
-eslint.config.mjs @scaleway/front-kernel
-pnpm-workspace.yaml @scaleway/front-kernel
-svgo.config.cjs @scaleway/front-kernel
-.github @scaleway/front-kernel
-.changeset @scaleway/front-kernel
-.aws @scaleway/front-kernel
+* @matthprost @lisalupi
+/packages/form @johnrazeur
+**/package.json @scaleway/front-kernel
+Dockerfile @scaleway/front-kernel
+babel.config.json @scaleway/front-kernel
+tsconfig.json @scaleway/front-kernel
+vite.config.ts @scaleway/front-kernel
+turbo.json @scaleway/front-kernel
+biome.json @scaleway/front-kernel
+eslint.config.mjs @scaleway/front-kernel
+pnpm-workspace.yaml @scaleway/front-kernel
+svgo.config.cjs @scaleway/front-kernel
+.github @scaleway/front-kernel
+.changeset/config.json @scaleway/front-kernel
+.aws @scaleway/front-kernel
diff --git a/packages/form/src/components/VerificationCodeField/__stories__/Playground.stories.tsx b/packages/form/src/components/VerificationCodeField/__stories__/Playground.stories.tsx
new file mode 100644
index 0000000000..5fd6bef5ce
--- /dev/null
+++ b/packages/form/src/components/VerificationCodeField/__stories__/Playground.stories.tsx
@@ -0,0 +1,5 @@
+import { Template } from './Template.stories'
+
+export const Playground = Template.bind({})
+
+Playground.args = Template.args
diff --git a/packages/form/src/components/VerificationCodeField/__stories__/Required.stories.tsx b/packages/form/src/components/VerificationCodeField/__stories__/Required.stories.tsx
new file mode 100644
index 0000000000..5a8e3ffe10
--- /dev/null
+++ b/packages/form/src/components/VerificationCodeField/__stories__/Required.stories.tsx
@@ -0,0 +1,17 @@
+import type { StoryFn } from '@storybook/react'
+import { Stack } from '@ultraviolet/ui'
+import type { ComponentProps } from 'react'
+import { VerificationCodeField } from '..'
+import { Submit } from '../../Submit'
+import { Template } from './Template.stories'
+
+export const Required: StoryFn<
+ ComponentProps
+> = args => (
+
+
+ Submit
+
+)
+
+Required.args = { ...Template.args, required: true }
diff --git a/packages/form/src/components/VerificationCodeField/__stories__/Template.stories.tsx b/packages/form/src/components/VerificationCodeField/__stories__/Template.stories.tsx
new file mode 100644
index 0000000000..5b86f897c0
--- /dev/null
+++ b/packages/form/src/components/VerificationCodeField/__stories__/Template.stories.tsx
@@ -0,0 +1,16 @@
+import type { StoryFn } from '@storybook/react'
+import { Stack } from '@ultraviolet/ui'
+import type { ComponentProps } from 'react'
+import { VerificationCodeField } from '..'
+import { Submit } from '../..'
+
+export const Template: StoryFn<
+ ComponentProps
+> = args => (
+
+
+ Submit
+
+)
+
+Template.args = { name: 'verification' }
diff --git a/packages/form/src/components/VerificationCodeField/__stories__/index.stories.tsx b/packages/form/src/components/VerificationCodeField/__stories__/index.stories.tsx
new file mode 100644
index 0000000000..95e6c24f1b
--- /dev/null
+++ b/packages/form/src/components/VerificationCodeField/__stories__/index.stories.tsx
@@ -0,0 +1,71 @@
+import type { Meta } from '@storybook/react'
+import { Snippet, Stack, Text } from '@ultraviolet/ui'
+import { Form, VerificationCodeField } from '../..'
+import { useForm } from '../../..'
+import { mockErrors } from '../../../mocks'
+
+export default {
+ component: VerificationCodeField,
+ decorators: [
+ ChildStory => {
+ const methods = useForm()
+ const {
+ errors,
+ isDirty,
+ isSubmitting,
+ touchedFields,
+ submitCount,
+ dirtyFields,
+ isValid,
+ isLoading,
+ isSubmitted,
+ isValidating,
+ isSubmitSuccessful,
+ } = methods.formState
+
+ return (
+
+ )
+ },
+ ],
+ title: 'Form/Components/Fields/VerificationCodeField',
+} as Meta
+
+export { Playground } from './Playground.stories'
+export { Required } from './Required.stories'
diff --git a/packages/form/src/components/VerificationCodeField/__tests__/__snapshots__/index.test.tsx.snap b/packages/form/src/components/VerificationCodeField/__tests__/__snapshots__/index.test.tsx.snap
new file mode 100644
index 0000000000..c33a8f8b2f
--- /dev/null
+++ b/packages/form/src/components/VerificationCodeField/__tests__/__snapshots__/index.test.tsx.snap
@@ -0,0 +1,159 @@
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
+
+exports[`VerificationCodeField > should render correctly 1`] = `
+
+ .emotion-0 {
+ display: -webkit-box;
+ display: -webkit-flex;
+ display: -ms-flexbox;
+ display: flex;
+ gap: 8px;
+ -webkit-flex-direction: column;
+ -ms-flex-direction: column;
+ flex-direction: column;
+ -webkit-align-items: center;
+ -webkit-box-align: center;
+ -ms-flex-align: center;
+ align-items: center;
+ -webkit-box-pack: center;
+ -ms-flex-pack: center;
+ -webkit-justify-content: center;
+ justify-content: center;
+ -webkit-box-flex-wrap: nowrap;
+ -webkit-flex-wrap: nowrap;
+ -ms-flex-wrap: nowrap;
+ flex-wrap: nowrap;
+}
+
+.emotion-2 {
+ font-size: 16px;
+ font-family: Inter,Asap,sans-serif;
+ font-weight: 400;
+ letter-spacing: 0;
+ line-height: 24px;
+ text-transform: none;
+ -webkit-text-decoration: none;
+ text-decoration: none;
+}
+
+.emotion-4 {
+ background: #ffffff;
+ border: solid 1px #d9dadd;
+ color: #3f4250;
+ font-size: 16px;
+ font-weight: 400;
+ text-align: center;
+ border-radius: 4px;
+ margin-right: 8px;
+ width: 40px;
+ height: 48px;
+ outline-style: none;
+ -webkit-transition: border-color 0.2s ease,box-shadow 0.2s ease;
+ transition: border-color 0.2s ease,box-shadow 0.2s ease;
+}
+
+.emotion-4:hover,
+.emotion-4:focus {
+ border-color: #792dd4;
+}
+
+.emotion-4:focus {
+ box-shadow: 0px 0px 0px 3px #8c40ef40;
+}
+
+.emotion-4:last-child {
+ margin-right: 0;
+}
+
+.emotion-4::-webkit-input-placeholder {
+ color: #727683;
+}
+
+.emotion-4::-moz-placeholder {
+ color: #727683;
+}
+
+.emotion-4:-ms-input-placeholder {
+ color: #727683;
+}
+
+.emotion-4::placeholder {
+ color: #727683;
+}
+
+
+
+`;
diff --git a/packages/form/src/components/VerificationCodeField/__tests__/index.test.tsx b/packages/form/src/components/VerificationCodeField/__tests__/index.test.tsx
new file mode 100644
index 0000000000..da7bee0ff3
--- /dev/null
+++ b/packages/form/src/components/VerificationCodeField/__tests__/index.test.tsx
@@ -0,0 +1,17 @@
+import { renderWithForm } from '@utils/test'
+import { describe, expect, test } from 'vitest'
+import { VerificationCodeField } from '..'
+
+describe('VerificationCodeField', () => {
+ test('should render correctly', () => {
+ const { asFragment } = renderWithForm(
+ ,
+ )
+ expect(asFragment()).toMatchSnapshot()
+ })
+})
diff --git a/packages/form/src/components/VerificationCodeField/index.tsx b/packages/form/src/components/VerificationCodeField/index.tsx
new file mode 100644
index 0000000000..173b76cb65
--- /dev/null
+++ b/packages/form/src/components/VerificationCodeField/index.tsx
@@ -0,0 +1,118 @@
+import { Stack, Text, VerificationCode } from '@ultraviolet/ui'
+import type { ComponentProps } from 'react'
+import type { FieldPath, FieldValues } from 'react-hook-form'
+import { useController } from 'react-hook-form'
+import { useErrors } from '../../providers'
+import type { BaseFieldProps } from '../../types'
+
+type VerificationCodeFieldProps<
+ TFieldValues extends FieldValues,
+ TName extends FieldPath,
+> = BaseFieldProps &
+ Partial<
+ Pick<
+ ComponentProps,
+ | 'disabled'
+ | 'error'
+ | 'fields'
+ | 'initialValue'
+ | 'onChange'
+ | 'onComplete'
+ | 'placeholder'
+ | 'required'
+ | 'type'
+ >
+ > & {
+ className?: string
+ id?: string
+ name: string
+ label?: string
+ }
+
+export const VerificationCodeField = <
+ TFieldValues extends FieldValues,
+ TName extends FieldPath = FieldPath,
+>({
+ className,
+ fields,
+ id = 'verification-code-input',
+ label,
+ name,
+ onChange,
+ onComplete,
+ placeholder,
+ required,
+ type = 'number',
+ disabled,
+ validate,
+}: VerificationCodeFieldProps) => {
+ const { getError } = useErrors()
+
+ const {
+ field,
+ fieldState: { error },
+ } = useController({
+ name,
+ rules: {
+ required,
+ validate: {
+ required: localValue => {
+ if (required && localValue.length !== (fields ?? 4)) {
+ return false
+ }
+
+ return true
+ },
+ ...validate,
+ },
+ },
+ })
+
+ return (
+
+ {label ? (
+
+ ) : null}
+
+ {
+ onChange?.(event)
+ field.onChange(event)
+ }}
+ onComplete={event => {
+ onComplete?.(event)
+ }}
+ type={type}
+ disabled={disabled}
+ required={required}
+ />
+ {error ? (
+
+ {getError({ label: label || 'verification-code-field' }, error)}
+
+ ) : null}
+
+ )
+}
diff --git a/packages/form/src/components/index.ts b/packages/form/src/components/index.ts
index c217ec375b..e7f4fffb85 100644
--- a/packages/form/src/components/index.ts
+++ b/packages/form/src/components/index.ts
@@ -21,3 +21,4 @@ export { SelectableCardGroupField } from './SelectableCardGroupField'
export { SelectInputFieldV2 } from './SelectInputFieldV2'
export { UnitInputField } from './UnitInputField'
export { SliderField } from './SliderField'
+export { VerificationCodeField } from './VerificationCodeField'