diff --git a/README.md b/README.md
index 8979d43..d3e988c 100644
--- a/README.md
+++ b/README.md
@@ -102,6 +102,7 @@ These changes improve throughput and reduce memory pressure when working with la
- [isSlug](#isslug) - Checks if a string is a valid slug
- [isTypeOf](#istypeof) - Checks if a file or URL has a valid extension for a given type
- [isIPv4](#isipv4) - Checks if a string is a valid IPv4 address
+- [isIPv6](#isipv6) - Checks if a string is a valid IPv6 address
- [isHexColor](#ishexcolor) - Checks if the input string is a valid hex color
- [isPalindrome](#ispalindrome) - Checks if the input string is a palindrome (ignores case, spaces, and punctuation)
- [isCoordinates](#iscoordinates) - Checks if given latitude and longitude are valid coordinates
@@ -725,6 +726,25 @@ isIPv4('192.168.1.a'); // false (non-numeric)
| --------- | ------ | -------- | -------------------------------------------- |
| text | string | required | The input string to validate as IPv4 address |
+#### `isIPv6(text)`
+
+Checks if a string is a valid IPv6 address.
+
+```javascript
+import { isIPv6 } from 'stringzy';
+
+isIPv6('2001:0db8:85a3:0000:0000:8a2e:0370:7334'); // true
+isIPv6('0:0:0:0:0:0:0:1'); // true
+isIPv6('2001:0db8:85a3:0000:0000:8a2e:0370:7334:1234'); // false (too many groups)
+isIPv6('2001:db8:::1'); // false (invalid use of shorthand)
+isIPv6('12345::abcd'); // false (out of range)
+isIPv6('2001:db8::g1'); // false (non-hex character)
+```
+
+| Parameter | Type | Default | Description |
+| --------- | ------ | -------- | -------------------------------------------- |
+| text | string | required | The input string to validate as IPv6 address |
+
#### `isHexColor(text)`
Checks if a string is a valid Hex color.
diff --git a/src/tests/validations/isIPv6.test.ts b/src/tests/validations/isIPv6.test.ts
new file mode 100644
index 0000000..a4a6da4
--- /dev/null
+++ b/src/tests/validations/isIPv6.test.ts
@@ -0,0 +1,71 @@
+import { describe, it } from 'node:test';
+import assert from 'node:assert';
+import { isIPv6 } from '../../validations/isIPv6';
+
+describe('isIPv6', () => {
+ it('returns true for valid full IPv6 addresses', () => {
+ assert.strictEqual(isIPv6('2001:0db8:85a3:0000:0000:8a2e:0370:7334'), true);
+ assert.strictEqual(isIPv6('ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff'), true);
+ assert.strictEqual(isIPv6('0:0:0:0:0:0:0:1'), true);
+ assert.strictEqual(isIPv6('fe80:0000:0000:0000:0202:b3ff:fe1e:8329'), true);
+ });
+
+ it('returns true for valid shorthand IPv6 addresses', () => {
+ assert.strictEqual(isIPv6('2001:db8:85a3::8a2e:370:7334'), true);
+ assert.strictEqual(isIPv6('::1'), true);
+ assert.strictEqual(isIPv6('fe80::1'), true);
+ assert.strictEqual(isIPv6('::'), true);
+ });
+
+ it('returns false for IPv6 addresses with too many groups', () => {
+ assert.strictEqual(isIPv6('2001:0db8:85a3:0000:0000:8a2e:0370:7334:1234'), false);
+ assert.strictEqual(isIPv6('1:2:3:4:5:6:7:8:9'), false);
+ });
+
+ it('returns false for IPv6 addresses with too few groups without shorthand', () => {
+ assert.strictEqual(isIPv6('2001:db8:85a3:8a2e:370:7334'), false);
+ assert.strictEqual(isIPv6('1:2:3:4:5:6:7'), false);
+ });
+
+ it('returns false for invalid use of shorthand (::)', () => {
+ assert.strictEqual(isIPv6('2001:db8:::1'), false);
+ assert.strictEqual(isIPv6(':::1'), false);
+ assert.strictEqual(isIPv6('2001::85a3::7334'), false);
+ });
+
+ it('returns false for groups longer than 4 hex digits', () => {
+ assert.strictEqual(isIPv6('12345::abcd'), false);
+ assert.strictEqual(isIPv6('2001:db8:85a3:00000:0000:8a2e:0370:7334'), false);
+ });
+
+ it('returns false for invalid characters', () => {
+ assert.strictEqual(isIPv6('2001:db8:85a3:z000:0000:8a2e:0370:7334'), false);
+ assert.strictEqual(isIPv6('2001:db8:85a3:0000:0000:8a2e:0370:g334'), false);
+ assert.strictEqual(isIPv6('abcd:efgh:ijkl:mnop:qrst:uvwx:yz12:3456'), false);
+ });
+
+ it('returns false for special characters and malformed strings', () => {
+ assert.strictEqual(isIPv6('2001:db8:85a3:0000:0000:8a2e:0370:7334:'), false);
+ assert.strictEqual(isIPv6(':2001:db8:85a3:0000:0000:8a2e:0370:7334'), false);
+ assert.strictEqual(isIPv6('2001:db8::85a3::7334'), false);
+ assert.strictEqual(isIPv6('2001:db8::85a3:7334:'), false);
+ });
+
+ it('returns false for empty strings and edge cases', () => {
+ assert.strictEqual(isIPv6(''), false);
+ assert.strictEqual(isIPv6(':'), false);
+ assert.strictEqual(isIPv6(':::'), false);
+ assert.strictEqual(isIPv6('::::'), false);
+ });
+
+ it('returns true for uppercase valid IPv6 addresses (case-insensitive)', () => {
+ assert.strictEqual(isIPv6('2001:DB8:85A3::8A2E:370:7334'), true);
+ assert.strictEqual(isIPv6('FE80::1'), true);
+ assert.strictEqual(isIPv6('FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF'), true);
+ });
+
+ it('returns false for IPv4-mapped IPv6 (unsupported by this validator)', () => {
+ assert.strictEqual(isIPv6('::ffff:192.168.1.1'), false);
+ assert.strictEqual(isIPv6('2001:db8::192.168.1.1'), false);
+ });
+});
diff --git a/src/validations/index.ts b/src/validations/index.ts
index 666e4a8..35754f4 100644
--- a/src/validations/index.ts
+++ b/src/validations/index.ts
@@ -5,6 +5,7 @@ export { isEmpty } from './isEmpty';
export { isSlug } from './isSlug';
export { isURL } from './isURL';
export { isIPv4 } from './isIPv4';
+export { isIPv6 } from './isIPv6';
export { isHexColor } from './isHexColor';
export { isPalindrome } from './isPalindrome'
export {isLowerCase} from './isLowerCase';
@@ -22,6 +23,7 @@ import { isEmpty } from './isEmpty';
import { isSlug } from './isSlug';
import { isURL } from './isURL';
import { isIPv4 } from './isIPv4';
+import { isIPv6 } from './isIPv6';
import { isHexColor } from './isHexColor';
import { isPalindrome } from './isPalindrome';
import { isLowerCase } from './isLowerCase';
@@ -40,6 +42,7 @@ export const validations = {
isSlug,
isURL,
isIPv4,
+ isIPv6,
isHexColor,
isPalindrome,
isLowerCase,
diff --git a/src/validations/isIPv6.ts b/src/validations/isIPv6.ts
new file mode 100644
index 0000000..ff63bbb
--- /dev/null
+++ b/src/validations/isIPv6.ts
@@ -0,0 +1,45 @@
+/**
+ * Checks if a given string is a valid IPv6 address.
+ *
+ * Valid IPv6 addresses consist of eight groups of four hexadecimal digits (0-9, a-f, A-F)
+ * separated by colons (:). Each group can have 1 to 4 hex digits.
+ * Leading zeros are allowed. IPv6 addresses can also use the "::" shorthand once
+ * to represent one or more groups of zeros.
+ *
+ * @param {string} str - The string to validate as an IPv6 address.
+ * @returns {boolean} `true` if the string is a valid IPv6 address, otherwise `false`.
+ *
+ * @example
+ * isIPv6("2001:0db8:85a3:0000:0000:8a2e:0370:7334"); // true
+ *
+ * @example
+ * isIPv6("2001:db8:85a3::8a2e:370:7334"); // true (uses shorthand)
+ *
+ * @example
+ * isIPv6("2001:db8:::1"); // false (invalid use of shorthand)
+ *
+ * @example
+ * isIPv6("12345::abcd"); // false (group too long)
+ */
+export function isIPv6(str: string): boolean {
+ const lower = str.toLowerCase();
+
+ if (!/^[0-9a-f:]+$/.test(lower)) return false;
+
+ const parts = lower.split("::");
+ if (parts.length > 2) return false;
+
+ const left = parts[0] ? parts[0].split(":") : [];
+ const right = parts[1] ? parts[1].split(":") : [];
+
+ // Each part (excluding shorthand) must be 1–4 hex digits
+ const validGroup = (g: string) => /^[0-9a-f]{1,4}$/.test(g);
+
+ if (!left.every(validGroup) || !right.every(validGroup)) return false;
+
+ const totalGroups = left.length + right.length;
+ if (parts.length === 1)
+ return totalGroups === 8;
+ else
+ return totalGroups < 8;
+}