diff --git a/packages/circuit-ui/components/ColorInput/ColorInput.mdx b/packages/circuit-ui/components/ColorInput/ColorInput.mdx
new file mode 100644
index 0000000000..abd9b15499
--- /dev/null
+++ b/packages/circuit-ui/components/ColorInput/ColorInput.mdx
@@ -0,0 +1,13 @@
+import { Meta, Status, Props, Story } from '../../../../.storybook/components';
+import * as Stories from './ColorInput.stories';
+
+
+
+# ColorInput
+
+
+
+The ColorInput component enables users to submit or select a color.
+
+
+
diff --git a/packages/circuit-ui/components/ColorInput/ColorInput.module.css b/packages/circuit-ui/components/ColorInput/ColorInput.module.css
new file mode 100644
index 0000000000..43e8eacb15
--- /dev/null
+++ b/packages/circuit-ui/components/ColorInput/ColorInput.module.css
@@ -0,0 +1,42 @@
+.suffix {
+ overflow: hidden;
+ border-top-right-radius: var(--cui-border-radius-byte);
+ border-bottom-right-radius: var(--cui-border-radius-byte);
+ pointer-events: auto !important;
+ border: none;
+ border-left: 1px solid var(--cui-border-normal);
+ width: var(--cui-spacings-exa);
+ height: var(--cui-spacings-exa);
+ position: absolute;
+ top: 0;
+ right: 0;
+ box-shadow: none;
+}
+
+.prefix {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ line-height: var(--cui-spacings-mega);
+}
+
+.colorInput {
+ opacity: 0;
+ width: var(--cui-spacings-exa);
+ height: var(--cui-spacings-exa);
+ border: none;
+ box-shadow: none;
+ padding: 0;
+}
+
+.input {
+ font-family: var(--cui-font-stack-mono);
+}
+
+.colorpick {
+ display: inline-block;
+}
+
+.colorpick:focus-within input {
+ box-shadow: 0 0 0 2px var(--cui-border-accent);
+}
\ No newline at end of file
diff --git a/packages/circuit-ui/components/ColorInput/ColorInput.spec.tsx b/packages/circuit-ui/components/ColorInput/ColorInput.spec.tsx
new file mode 100644
index 0000000000..13cd2821d2
--- /dev/null
+++ b/packages/circuit-ui/components/ColorInput/ColorInput.spec.tsx
@@ -0,0 +1,39 @@
+/**
+ * Copyright 2019, SumUp Ltd.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { describe, expect, it } from 'vitest';
+import { createRef } from 'react';
+
+import { render, axe } from '../../util/test-utils.js';
+import type { InputElement } from '../Input/index.js';
+
+import { ColorInput } from './ColorInput.js';
+
+describe('SearchInput', () => {
+ const baseProps = { label: 'Car color', pickerLabel: 'Pick car color' };
+
+ it('should forward a ref', () => {
+ const ref = createRef();
+ const { container } = render();
+ const input = container.querySelector("input[type='color']");
+ expect(ref.current).toBe(input);
+ });
+
+ it('should have no accessibility violations', async () => {
+ const { container } = render();
+ const actual = await axe(container);
+ expect(actual).toHaveNoViolations();
+ });
+});
diff --git a/packages/circuit-ui/components/ColorInput/ColorInput.stories.tsx b/packages/circuit-ui/components/ColorInput/ColorInput.stories.tsx
new file mode 100644
index 0000000000..d5411c4424
--- /dev/null
+++ b/packages/circuit-ui/components/ColorInput/ColorInput.stories.tsx
@@ -0,0 +1,33 @@
+/**
+ * Copyright 2019, SumUp Ltd.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { ColorInput, type ColorInputProps } from './ColorInput.js';
+
+export default {
+ title: 'Forms/Input/ColorInput',
+ component: ColorInput,
+};
+
+const baseArgs = {
+ label: 'Color',
+ pickerLabel: 'Pick color',
+ placeholder: 'abc321',
+};
+
+export const Base = (args: ColorInputProps) => (
+
+);
+
+Base.args = baseArgs;
diff --git a/packages/circuit-ui/components/ColorInput/ColorInput.tsx b/packages/circuit-ui/components/ColorInput/ColorInput.tsx
new file mode 100644
index 0000000000..f01c53c31a
--- /dev/null
+++ b/packages/circuit-ui/components/ColorInput/ColorInput.tsx
@@ -0,0 +1,142 @@
+/**
+ * Copyright 2024, SumUp Ltd.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+'use client';
+
+import {
+ forwardRef,
+ useCallback,
+ useEffect,
+ useId,
+ useRef,
+ useState,
+ type ChangeEventHandler,
+} from 'react';
+
+import { Input, type InputElement, type InputProps } from '../Input/index.js';
+import { clsx } from '../../styles/clsx.js';
+import { FieldLabel, FieldLabelText } from '../Field/index.js';
+import { applyMultipleRefs } from '../../util/refs.js';
+
+import styles from './ColorInput.module.css';
+
+export interface ColorInputProps
+ extends Omit<
+ InputProps,
+ | 'ref'
+ | 'type'
+ | 'defaultValue'
+ | 'value'
+ | 'placeholder'
+ | 'maxLength'
+ | 'pattern'
+ | 'renderPrefix'
+ | 'renderSuffix'
+ | 'as'
+ > {
+ /**
+ * A short string that is shown inside the empty input.
+ */
+ placeholder?: string;
+ /**
+ * The value of the input element.
+ */
+ value?: string;
+ /**
+ * The default value of the input element.
+ */
+ defaultValue?: string;
+ /*
+ * Picker label describes the color picker which serves as an alternative
+ * to the hex color input.
+ */
+ pickerLabel: string;
+}
+
+export const ColorInput = forwardRef(
+ (
+ { onChange, className, value, defaultValue, pickerLabel, ...props },
+ ref,
+ ) => {
+ const [currentColor, setCurrentColor] = useState(
+ defaultValue,
+ );
+ const colorDisplayRef = useRef(null);
+ const colorPickerRef = useRef(null);
+ const pickerId = useId();
+
+ const onPickerColorChange: ChangeEventHandler = (e) => {
+ setCurrentColor(e.target.value);
+ if (onChange) {
+ onChange(e);
+ }
+ };
+
+ const onInputChange: ChangeEventHandler = (e) => {
+ if (colorPickerRef.current) {
+ colorPickerRef.current.value = `#${e.target.value}`;
+ }
+ setCurrentColor(`#${e.target.value}`);
+ };
+
+ useEffect(() => {
+ if (colorDisplayRef.current && currentColor) {
+ colorDisplayRef.current.style.backgroundColor = currentColor;
+ }
+ }, [currentColor]);
+
+ const renderSuffix = useCallback(
+ () => (
+
+
+
+
+
+
+ ),
+ [],
+ );
+
+ return (
+ (
+
+ #
+
+ )}
+ renderSuffix={renderSuffix}
+ value={currentColor ? currentColor.replace('#', '') : undefined}
+ inputClassName={styles.input}
+ maxLength={6}
+ pattern="[0-9a-f]{3,6}"
+ onChange={onInputChange}
+ {...props}
+ />
+ );
+ },
+);
+
+ColorInput.displayName = 'ColorInput';
diff --git a/packages/circuit-ui/components/ColorInput/index.ts b/packages/circuit-ui/components/ColorInput/index.ts
new file mode 100644
index 0000000000..4e31720a5c
--- /dev/null
+++ b/packages/circuit-ui/components/ColorInput/index.ts
@@ -0,0 +1,18 @@
+/**
+ * Copyright 2019, SumUp Ltd.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export { ColorInput } from './ColorInput.js';
+
+export type { ColorInputProps } from './ColorInput.js';