UI Label Colors

Let’s create a simple label color UI for an issue tracker.

The labels are styled with a tint color based on the hue, and the color picker is a simple <RangeSlider /> with a float label to show the hue value.

All of this is achievable in any JS framework (or vanilla), but I’m demonstrating it with Svelte here as it’s the simplest framework to work with.

UI Label Colors

LabelColors.svelte

The general UI layout for this recipe. We show a grid of labels with <input> fields for the label name and a <button> to open the color picker.

Both the <input> and <RangeSlider /> are bound to the same hue variable, and the <RangeSlider /> simply allows a min and max of the hue property (0-360).

<script>

  let labels = [
    { name: 'Bug', hue: 20, isOpen: false },
    { name: 'Feature', hue: 140, isOpen: false },
    { name: 'Documentation', hue: 202, isOpen: true },
    { name: 'Maintenance', hue: 230, isOpen: false },
    { name: 'Question', hue: 320, isOpen: false },
    { name: 'Invalid', hue: 0, isOpen: false },
  ];

  const openPicker = (index) => labels[index].isOpen = true;
  const closePicker = (index) => labels[index].isOpen = false;

</script>

<ul class="labels" data-grid>
  {#each labels as label,i}
    <li class="label" data-subgrid style="--hue: {label.hue}">
      <input class="label-name" type="text" bind:value={label.name} />
      <button class="label-color" type="button" 
        on:click={() => openPicker(i)}
        use:clickOutside={() => closePicker(i)}
      >
        <Icon icon="tabler:square-rounded-filled" />
        <LabelColorSlider bind:hue={label.hue} bind:isOpen={label.isOpen} />
      </button>
    </li>
  {/each}
</ul>

LabelColorSlider.svelte

This is a simple sub-component which displays a nice little popover with a <RangeSlider /> inside.

<script>
  export let hue = 0;
  export let isOpen = false;
</script>

<div class="label-slider-container" class:isOpen>
  <RangeSlider
    class="label-slider"
    bind:value={hue}
    pips
    all={false}
    rest="pips"
    pipstep={20}
    min={0}
    max={320}
    float
    handleFormatter={() => `
      <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M12 2q-.327 0-.642.005l-.616.017l-.299.013l-.579.034l-.553.046c-4.785.464-6.732 2.411-7.196 7.196l-.046.553l-.034.579q-.008.147-.013.299l-.017.616l-.004.318L2 12q0 .327.005.642l.017.616l.013.299l.034.579l.046.553c.464 4.785 2.411 6.732 7.196 7.196l.553.046l.579.034q.147.008.299.013l.616.017L12 22l.642-.005l.616-.017l.299-.013l.579-.034l.553-.046c4.785-.464 6.732-2.411 7.196-7.196l.046-.553l.034-.579q.008-.147.013-.299l.017-.616L22 12l-.005-.642l-.017-.616l-.013-.299l-.034-.579l-.046-.553c-.464-4.785-2.411-6.732-7.196-7.196l-.553-.046l-.579-.034l-.299-.013l-.616-.017l-.318-.004z"/></svg>
    `}
  />
</div>