diff --git a/src/tools/color-converter/color-converter.e2e.spec.ts b/src/tools/color-converter/color-converter.e2e.spec.ts
new file mode 100644
index 00000000..6ab91d7a
--- /dev/null
+++ b/src/tools/color-converter/color-converter.e2e.spec.ts
@@ -0,0 +1,23 @@
+import { expect, test } from '@playwright/test';
+
+test.describe('Tool - Color converter', () => {
+ test.beforeEach(async ({ page }) => {
+ await page.goto('/color-converter');
+ });
+
+ test('Has title', async ({ page }) => {
+ await expect(page).toHaveTitle('Color converter - IT Tools');
+ });
+
+ test('Color is converted from its name to other formats', async ({ page }) => {
+ await page.getByTestId('input-name').fill('olive');
+
+ expect(await page.getByTestId('input-name').inputValue()).toEqual('olive');
+ expect(await page.getByTestId('input-hex').inputValue()).toEqual('#808000');
+ expect(await page.getByTestId('input-rgb').inputValue()).toEqual('rgb(128, 128, 0)');
+ expect(await page.getByTestId('input-hsl').inputValue()).toEqual('hsl(60, 100%, 25%)');
+ expect(await page.getByTestId('input-hwb').inputValue()).toEqual('hwb(60 0% 50%)');
+ expect(await page.getByTestId('input-cmyk').inputValue()).toEqual('device-cmyk(0% 0% 100% 50%)');
+ expect(await page.getByTestId('input-lch').inputValue()).toEqual('lch(52.15% 56.81 99.57)');
+ });
+});
diff --git a/src/tools/color-converter/color-converter.models.test.ts b/src/tools/color-converter/color-converter.models.test.ts
new file mode 100644
index 00000000..4261fed1
--- /dev/null
+++ b/src/tools/color-converter/color-converter.models.test.ts
@@ -0,0 +1,13 @@
+import { describe, expect, it } from 'vitest';
+import { removeAlphaChannelWhenOpaque } from './color-converter.models';
+
+describe('color-converter models', () => {
+ describe('removeAlphaChannelWhenOpaque', () => {
+ it('remove alpha channel of an hex color when it is opaque (alpha = 1)', () => {
+ expect(removeAlphaChannelWhenOpaque('#000000ff')).toBe('#000000');
+ expect(removeAlphaChannelWhenOpaque('#ffffffFF')).toBe('#ffffff');
+ expect(removeAlphaChannelWhenOpaque('#000000FE')).toBe('#000000FE');
+ expect(removeAlphaChannelWhenOpaque('#00000000')).toBe('#00000000');
+ });
+ });
+});
diff --git a/src/tools/color-converter/color-converter.models.ts b/src/tools/color-converter/color-converter.models.ts
new file mode 100644
index 00000000..030fd074
--- /dev/null
+++ b/src/tools/color-converter/color-converter.models.ts
@@ -0,0 +1,52 @@
+import { type Colord, colord } from 'colord';
+import { withDefaultOnError } from '@/utils/defaults';
+import { useValidation } from '@/composable/validation';
+
+export { removeAlphaChannelWhenOpaque, buildColorFormat };
+
+function removeAlphaChannelWhenOpaque(hexColor: string) {
+ return hexColor.replace(/^(#(?:[0-9a-f]{3}){1,2})ff$/i, '$1');
+}
+
+function buildColorFormat({
+ label,
+ parse = value => colord(value),
+ format,
+ placeholder,
+ invalidMessage = `Invalid ${label.toLowerCase()} format.`,
+ type = 'text',
+}: {
+ label: string
+ parse?: (value: string) => Colord
+ format: (value: Colord) => string
+ placeholder?: string
+ invalidMessage?: string
+ type?: 'text' | 'color-picker'
+}) {
+ const value = ref('');
+
+ return {
+ type,
+ label,
+ parse: (v: string) => withDefaultOnError(() => parse(v), undefined),
+ format,
+ placeholder,
+ value,
+ validation: useValidation({
+ source: value,
+ rules: [
+ {
+ message: invalidMessage,
+ validator: v => withDefaultOnError(() => {
+ if (v === '') {
+ return true;
+ }
+
+ return parse(v).isValid();
+ }, false),
+ },
+ ],
+ }),
+
+ };
+}
diff --git a/src/tools/color-converter/color-converter.vue b/src/tools/color-converter/color-converter.vue
index 0b9909fa..a7d6139f 100644
--- a/src/tools/color-converter/color-converter.vue
+++ b/src/tools/color-converter/color-converter.vue
@@ -1,87 +1,103 @@
-
-
+
+ updateColorValue(parse(v), key)"
+ />
+
+
onInputUpdated(v, 'hex')"
+ @update:value="(v:string) => updateColorValue(parse(v), key)"
/>
-
- onInputUpdated(v, 'name')" />
-
-
- onInputUpdated(v, 'hex')" />
-
-
- onInputUpdated(v, 'rgb')" />
-
-
- onInputUpdated(v, 'hsl')" />
-
-
- onInputUpdated(v, 'hwb')" />
-
-
- onInputUpdated(v, 'lch')" />
-
-
- onInputUpdated(v, 'cmyk')" />
-
-
+