> ## Documentation Index
> Fetch the complete documentation index at: https://bettertickets.gg/llms.txt
> Use this file to discover all available pages before exploring further.

# Discord Color Picker

> Free Discord color picker for gradient or solid name colors. Pick two colors, preview your username live, extract colors from an image and copy the hex codes.

export const ColorPicker = () => {
  const hsvToRgb = (h, s, v) => {
    s /= 100;
    v /= 100;
    const c = v * s;
    const x = c * (1 - Math.abs(h / 60 % 2 - 1));
    const m = v - c;
    let r = 0;
    let g = 0;
    let b = 0;
    if (h < 60) [r, g, b] = [c, x, 0]; else if (h < 120) [r, g, b] = [x, c, 0]; else if (h < 180) [r, g, b] = [0, c, x]; else if (h < 240) [r, g, b] = [0, x, c]; else if (h < 300) [r, g, b] = [x, 0, c]; else [r, g, b] = [c, 0, x];
    return {
      r: Math.round((r + m) * 255),
      g: Math.round((g + m) * 255),
      b: Math.round((b + m) * 255)
    };
  };
  const rgbToHex = ({r, g, b}) => `#${[r, g, b].map(v => v.toString(16).padStart(2, "0")).join("")}`.toUpperCase();
  const hexToRgb = hex => {
    const clean = hex.trim().replace(/^#/, "");
    if (!(/^[0-9a-fA-F]{6}$/).test(clean)) return null;
    return {
      r: Number.parseInt(clean.slice(0, 2), 16),
      g: Number.parseInt(clean.slice(2, 4), 16),
      b: Number.parseInt(clean.slice(4, 6), 16)
    };
  };
  const rgbToHsv = ({r, g, b}) => {
    r /= 255;
    g /= 255;
    b /= 255;
    const max = Math.max(r, g, b);
    const min = Math.min(r, g, b);
    const d = max - min;
    let h = 0;
    if (d !== 0) {
      if (max === r) h = (g - b) / d % 6 * 60; else if (max === g) h = ((b - r) / d + 2) * 60; else h = ((r - g) / d + 4) * 60;
      if (h < 0) h += 360;
    }
    const s = max === 0 ? 0 : d / max;
    return {
      h,
      s: s * 100,
      v: max * 100
    };
  };
  const boxClass = "rounded-lg border dark:border-zinc-950/80 border-zinc-950/10 bg-zinc-950/2 dark:bg-white/5";
  const [mode, setMode] = useState("gradient");
  const [modeOpen, setModeOpen] = useState(false);
  const [color1, setColor1] = useState({
    h: 0,
    s: 95,
    v: 46
  });
  const [color2, setColor2] = useState({
    h: 0,
    s: 34,
    v: 88
  });
  const [name, setName] = useState("DiscordUser");
  const [hexInput1, setHexInput1] = useState("");
  const [hexInput2, setHexInput2] = useState("");
  const [imageEl, setImageEl] = useState(null);
  const [imagePreviewUrl, setImagePreviewUrl] = useState(null);
  const [palettePairs, setPalettePairs] = useState([]);
  const [copied, setCopied] = useState(false);
  const [dragTarget, setDragTarget] = useState(null);
  const square1Ref = useRef(null);
  const square2Ref = useRef(null);
  const hue1Ref = useRef(null);
  const hue2Ref = useRef(null);
  const fileInputRef = useRef(null);
  const rgb1 = hsvToRgb(color1.h, color1.s, color1.v);
  const rgb2 = hsvToRgb(color2.h, color2.s, color2.v);
  const hex1 = rgbToHex(rgb1);
  const hex2 = rgbToHex(rgb2);
  useEffect(() => {
    setHexInput1(hex1);
  }, [hex1]);
  useEffect(() => {
    setHexInput2(hex2);
  }, [hex2]);
  useEffect(() => {
    if (!dragTarget) return;
    const move = e => {
      if (dragTarget === "square1" || dragTarget === "square2") {
        const ref = dragTarget === "square1" ? square1Ref : square2Ref;
        const setColor = dragTarget === "square1" ? setColor1 : setColor2;
        const current = dragTarget === "square1" ? color1 : color2;
        if (!ref.current) return;
        const rect = ref.current.getBoundingClientRect();
        const x = Math.min(Math.max(e.clientX - rect.left, 0), rect.width);
        const y = Math.min(Math.max(e.clientY - rect.top, 0), rect.height);
        setColor({
          ...current,
          s: x / rect.width * 100,
          v: 100 - y / rect.height * 100
        });
      } else {
        const ref = dragTarget === "hue1" ? hue1Ref : hue2Ref;
        const setColor = dragTarget === "hue1" ? setColor1 : setColor2;
        const current = dragTarget === "hue1" ? color1 : color2;
        if (!ref.current) return;
        const rect = ref.current.getBoundingClientRect();
        const x = Math.min(Math.max(e.clientX - rect.left, 0), rect.width);
        setColor({
          ...current,
          h: x / rect.width * 360
        });
      }
    };
    const up = () => setDragTarget(null);
    window.addEventListener("pointermove", move);
    window.addEventListener("pointerup", up);
    return () => {
      window.removeEventListener("pointermove", move);
      window.removeEventListener("pointerup", up);
    };
  }, [dragTarget, color1, color2]);
  const handleImageSelect = e => {
    const file = e.target.files?.[0];
    if (!file) return;
    const url = URL.createObjectURL(file);
    setImagePreviewUrl(url);
    setPalettePairs([]);
    const img = new window.Image();
    img.onload = () => setImageEl(img);
    img.src = url;
  };
  const clearImage = () => {
    setImageEl(null);
    setImagePreviewUrl(null);
    setPalettePairs([]);
    if (fileInputRef.current) fileInputRef.current.value = "";
  };
  const extractColors = () => {
    if (!imageEl) return;
    const size = 80;
    const canvas = document.createElement("canvas");
    canvas.width = size;
    canvas.height = size;
    const ctx = canvas.getContext("2d");
    ctx.drawImage(imageEl, 0, 0, size, size);
    const {data} = ctx.getImageData(0, 0, size, size);
    const buckets = new Map();
    for (let i = 0; i < data.length; i += 4) {
      const a = data[i + 3];
      if (a < 128) continue;
      const r = data[i];
      const g = data[i + 1];
      const b = data[i + 2];
      const key = `${r >> 4}-${g >> 4}-${b >> 4}`;
      const bucket = buckets.get(key);
      if (bucket) bucket.count += 1; else buckets.set(key, {
        r,
        g,
        b,
        count: 1
      });
    }
    const sorted = [...buckets.values()].sort((a, b) => b.count - a.count);
    if (sorted.length === 0) return;
    const distinct = [];
    const minDistSq = 40 ** 2;
    for (const c of sorted) {
      if (distinct.length >= 8) break;
      const tooClose = distinct.some(d => (d.r - c.r) ** 2 + (d.g - c.g) ** 2 + (d.b - c.b) ** 2 < minDistSq);
      if (!tooClose) distinct.push(c);
    }
    if (distinct.length < 2) {
      setColor1(rgbToHsv(distinct[0] ?? sorted[0]));
      setColor2(rgbToHsv(sorted[sorted.length - 1]));
      return;
    }
    const pairs = distinct.map((c, i) => [c, distinct[(i + 1) % distinct.length]]);
    setPalettePairs(pairs);
    setColor1(rgbToHsv(pairs[0][0]));
    setColor2(rgbToHsv(pairs[0][1]));
  };
  const pickWithEyedropper = async (onChange, current) => {
    if (typeof window === "undefined" || !window.EyeDropper) return;
    try {
      const dropper = new window.EyeDropper();
      const result = await dropper.open();
      const rgb = hexToRgb(result.sRGBHex);
      if (rgb) onChange(rgbToHsv(rgb));
    } catch {}
  };
  const commitHex = (value, onChange, fallback, setInput) => {
    const rgb = hexToRgb(value);
    if (rgb) onChange(rgbToHsv(rgb)); else setInput(fallback);
  };
  const hasEyedropper = typeof window !== "undefined" && !!window.EyeDropper;
  const nameNode = <span style={{
    backgroundImage: mode === "solid" ? `linear-gradient(90deg, ${hex1}, ${hex1})` : `repeating-linear-gradient(90deg, ${hex1} 0px, ${hex2} 30px, ${hex1} 60px)`,
    backgroundSize: "60px 100%",
    WebkitBackgroundClip: "text",
    backgroundClip: "text",
    color: "transparent",
    WebkitTextFillColor: "transparent"
  }}>
      {name}
    </span>;
  const handleCopy = () => {
    const text = mode === "gradient" ? `${hex1} ${hex2}` : hex1;
    if (!navigator.clipboard) return;
    navigator.clipboard.writeText(text).then(() => {
      setCopied(true);
      setTimeout(() => setCopied(false), 1500);
    });
  };
  const avatarHue = [...name || "?"].reduce((acc, c) => acc + c.charCodeAt(0), 0) % 360;
  return <div className="not-prose space-y-5 rounded-2xl border dark:border-zinc-950/80 border-zinc-950/10 bg-zinc-950/[0.015] p-5 shadow-sm dark:bg-white/[0.02]">
      <div className="flex justify-center">
        <div className="relative">
          <button type="button" onClick={() => setModeOpen(o => !o)} className={`${boxClass} flex items-center gap-1.5 px-3 py-1.5 font-semibold text-sm`}>
            {mode === "gradient" ? "Gradient" : "Solid"}
            <svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
              <path d="m6 9 6 6 6-6" />
            </svg>
          </button>
          {modeOpen ? <div className={`${boxClass} absolute z-10 mt-1 w-full overflow-hidden bg-white dark:bg-zinc-900`}>
              {["gradient", "solid"].map(m => <button key={m} type="button" onClick={() => {
    setMode(m);
    setModeOpen(false);
  }} className="block w-full px-3 py-1.5 text-left text-sm capitalize hover:bg-zinc-950/5 dark:hover:bg-white/10">
                  {m}
                </button>)}
            </div> : null}
        </div>
      </div>

      <div className="flex flex-wrap items-center justify-center gap-2">
        <button type="button" onClick={() => fileInputRef.current?.click()} className={`${boxClass} px-3 py-1.5 text-sm font-medium`}>
          {imageEl ? "Change image" : "Select image to extract colors from"}
        </button>
        <input ref={fileInputRef} type="file" accept="image/*" className="hidden" onChange={handleImageSelect} />
        <button type="button" onClick={extractColors} disabled={!imageEl} className={`${boxClass} px-3 py-1.5 text-sm font-medium disabled:cursor-not-allowed disabled:opacity-40`}>
          Extract colors
        </button>
      </div>

      {palettePairs.length > 0 ? <div className="flex flex-wrap justify-center gap-2">
          {palettePairs.map((pair, i) => <button key={i} type="button" onClick={() => {
    setColor1(rgbToHsv(pair[0]));
    setColor2(rgbToHsv(pair[1]));
  }} title={`${rgbToHex(pair[0])} / ${rgbToHex(pair[1])}`} className="size-8 shrink-0 rounded-full border-2 border-white/70 shadow dark:border-zinc-950/70" style={{
    background: `linear-gradient(135deg, ${rgbToHex(pair[0])} 50%, ${rgbToHex(pair[1])} 50%)`
  }} />)}
        </div> : null}

      <div className="flex flex-wrap justify-center gap-4">
        {imagePreviewUrl ? <div className="relative h-auto w-40 shrink-0 overflow-hidden rounded-lg">
            <img src={imagePreviewUrl} alt="Selected for color extraction" className="h-full w-full object-cover" />
            <button type="button" onClick={clearImage} title="Remove image" className="absolute top-1 right-1 flex size-5 items-center justify-center rounded-full bg-red-500 text-white shadow">
              <svg viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" strokeWidth="3" strokeLinecap="round">
                <path d="M18 6 6 18" />
                <path d="m6 6 12 12" />
              </svg>
            </button>
          </div> : null}
        <div className={`${boxClass} min-w-[220px] flex-1 space-y-3 p-3`}>
          <div ref={square1Ref} className="relative h-36 w-full select-none rounded-lg" style={{
    backgroundColor: rgbToHex(hsvToRgb(color1.h, 100, 100)),
    backgroundImage: "linear-gradient(to top, #000, transparent), linear-gradient(to right, #fff, transparent)",
    touchAction: "none",
    cursor: "crosshair"
  }} onPointerDown={e => {
    setDragTarget("square1");
    const rect = e.currentTarget.getBoundingClientRect();
    const x = Math.min(Math.max(e.clientX - rect.left, 0), rect.width);
    const y = Math.min(Math.max(e.clientY - rect.top, 0), rect.height);
    setColor1({
      ...color1,
      s: x / rect.width * 100,
      v: 100 - y / rect.height * 100
    });
  }}>
            <div className="absolute size-4 rounded-full border-2 border-white shadow" style={{
    left: `${color1.s}%`,
    top: `${100 - color1.v}%`,
    transform: "translate(-50%, -50%)",
    backgroundColor: hex1
  }} />
          </div>
          <div ref={hue1Ref} className="relative h-3 w-full select-none rounded-full" style={{
    background: "linear-gradient(to right, #f00, #ff0, #0f0, #0ff, #00f, #f0f, #f00)",
    touchAction: "none",
    cursor: "pointer"
  }} onPointerDown={e => {
    setDragTarget("hue1");
    const rect = e.currentTarget.getBoundingClientRect();
    const x = Math.min(Math.max(e.clientX - rect.left, 0), rect.width);
    setColor1({
      ...color1,
      h: x / rect.width * 360
    });
  }}>
            <div className="absolute top-1/2 size-4 rounded-full border-2 border-white shadow" style={{
    left: `${color1.h / 360 * 100}%`,
    transform: "translate(-50%, -50%)",
    backgroundColor: `hsl(${color1.h}, 100%, 50%)`
  }} />
          </div>
          <div className="flex gap-2">
            <button type="button" onClick={() => pickWithEyedropper(setColor1, color1)} disabled={!hasEyedropper} title={hasEyedropper ? "Pick a color from your screen" : "Not supported in this browser"} className={`${boxClass} flex items-center justify-center px-2.5 disabled:cursor-not-allowed disabled:opacity-30`}>
              <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
                <path d="m2 22 1-4 9.5-9.5" />
                <path d="M13.5 7.5 17 4a2.121 2.121 0 0 1 3 3l-3.5 3.5" />
                <path d="m9.5 12.5 4-4" />
                <path d="M3 21.5 6.5 20" />
              </svg>
            </button>
            <input className={`${boxClass} flex-1 px-3 py-2 text-center font-mono text-sm uppercase`} value={hexInput1} onChange={e => setHexInput1(e.target.value)} onBlur={e => commitHex(e.target.value, setColor1, hex1, setHexInput1)} onKeyDown={e => {
    if (e.key === "Enter") commitHex(e.currentTarget.value, setColor1, hex1, setHexInput1);
  }} spellCheck={false} />
          </div>
        </div>

        {mode === "gradient" ? <div className={`${boxClass} min-w-[220px] flex-1 space-y-3 p-3`}>
            <div ref={square2Ref} className="relative h-36 w-full select-none rounded-lg" style={{
    backgroundColor: rgbToHex(hsvToRgb(color2.h, 100, 100)),
    backgroundImage: "linear-gradient(to top, #000, transparent), linear-gradient(to right, #fff, transparent)",
    touchAction: "none",
    cursor: "crosshair"
  }} onPointerDown={e => {
    setDragTarget("square2");
    const rect = e.currentTarget.getBoundingClientRect();
    const x = Math.min(Math.max(e.clientX - rect.left, 0), rect.width);
    const y = Math.min(Math.max(e.clientY - rect.top, 0), rect.height);
    setColor2({
      ...color2,
      s: x / rect.width * 100,
      v: 100 - y / rect.height * 100
    });
  }}>
              <div className="absolute size-4 rounded-full border-2 border-white shadow" style={{
    left: `${color2.s}%`,
    top: `${100 - color2.v}%`,
    transform: "translate(-50%, -50%)",
    backgroundColor: hex2
  }} />
            </div>
            <div ref={hue2Ref} className="relative h-3 w-full select-none rounded-full" style={{
    background: "linear-gradient(to right, #f00, #ff0, #0f0, #0ff, #00f, #f0f, #f00)",
    touchAction: "none",
    cursor: "pointer"
  }} onPointerDown={e => {
    setDragTarget("hue2");
    const rect = e.currentTarget.getBoundingClientRect();
    const x = Math.min(Math.max(e.clientX - rect.left, 0), rect.width);
    setColor2({
      ...color2,
      h: x / rect.width * 360
    });
  }}>
              <div className="absolute top-1/2 size-4 rounded-full border-2 border-white shadow" style={{
    left: `${color2.h / 360 * 100}%`,
    transform: "translate(-50%, -50%)",
    backgroundColor: `hsl(${color2.h}, 100%, 50%)`
  }} />
            </div>
            <div className="flex gap-2">
              <button type="button" onClick={() => pickWithEyedropper(setColor2, color2)} disabled={!hasEyedropper} title={hasEyedropper ? "Pick a color from your screen" : "Not supported in this browser"} className={`${boxClass} flex items-center justify-center px-2.5 disabled:cursor-not-allowed disabled:opacity-30`}>
                <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
                  <path d="m2 22 1-4 9.5-9.5" />
                  <path d="M13.5 7.5 17 4a2.121 2.121 0 0 1 3 3l-3.5 3.5" />
                  <path d="m9.5 12.5 4-4" />
                  <path d="M3 21.5 6.5 20" />
                </svg>
              </button>
              <input className={`${boxClass} flex-1 px-3 py-2 text-center font-mono text-sm uppercase`} value={hexInput2} onChange={e => setHexInput2(e.target.value)} onBlur={e => commitHex(e.target.value, setColor2, hex2, setHexInput2)} onKeyDown={e => {
    if (e.key === "Enter") commitHex(e.currentTarget.value, setColor2, hex2, setHexInput2);
  }} spellCheck={false} />
            </div>
          </div> : null}
      </div>

      <input className={`${boxClass} w-full px-3 py-2 text-sm`} value={name} onChange={e => setName(e.target.value)} placeholder="Your Discord name" spellCheck={false} />

      <div className="flex flex-wrap items-center gap-3">
        {imagePreviewUrl ? <img src={imagePreviewUrl} alt="" className="size-8 shrink-0 rounded-full object-cover" /> : <span className="flex size-8 shrink-0 items-center justify-center rounded-full font-semibold text-sm text-white" style={{
    backgroundColor: `hsl(${avatarHue}, 65%, 55%)`
  }}>
            {(name || "?").charAt(0).toUpperCase()}
          </span>}
        <span className="font-semibold">{nameNode}</span>
        <span className="font-mono text-xs text-zinc-950/60 dark:text-white/60">
          {hex1}
          {mode === "gradient" ? ` ${hex2}` : ""}
        </span>
        <button type="button" onClick={handleCopy} className={`${boxClass} px-2.5 py-1 text-xs font-semibold ${copied ? "bg-[#00FF1E]" : ""}`}>
          {copied ? "Copied!" : "Copy"}
        </button>
      </div>
      <p className="text-xs text-zinc-950/50 dark:text-white/50">
        Not confirmed to look exactly like this on Discord.
      </p>
    </div>;
};

Discord nitro lets you set your display name to a solid color and let you choose between a solid color, a gradient and a holographic color for a discord roles color. This tool lets you pick colors, preview roughly how your name will look and copy the hex codes to paste into Discord's own name color picker.

## Picker

Drag inside the color square to set saturation and brightness, drag the bar below it to set hue or type a hex code directly. The eyedropper button (where supported by your browser) lets you pick a color from anywhere on your screen, not just this page.

<ColorPicker />

## How to use the colors on Discord

1. Pick your color(s) above, or upload an image and hit **Extract colors** to pull two colors out of it automatically.
2. Hit **Copy** to copy the hex code(s).
3. You can then paste the colors for your discord server roles.

## Gradient vs. solid

**Solid** sets your whole display name to one color. **Gradient** blends between two colors across your name, character by character, which is what most people mean by a "gradient name" on Discord.

<Note>
  The preview above is a linear-RGB approximation of Discord's gradient rendering, not a pixel-perfect copy. Colors on Discord can also read slightly differently depending on light/dark theme and your display.
</Note>

## Extracting colors from an image

Select an image and hit **Extract colors** to pull two representative colors out of it. One dominant color and one contrasting color, chosen automatically from the image's pixels. This all happens locally in your browser. The image is never uploaded anywhere.


## Related topics

- [Settings](/docs/roleplay/settings.md)
- [FAQ](/docs/faq.md)
- [Introduction](/docs/introduction.md)
