Label is superimposed on the input with ReactJS + TailwindCSS

I have an input with a label that always goes up when the user selects the input to type something.

The only problem is that the label always goes down when the input is taken out of focus.

const MyApp = () => {
  return(
    <div>
      <label className="relative flex min-h-10">
        <input
          className={
            "flex flex-1 p-2 bg-white disabled:bg-[#9b9b9b] text-black transition-[outline-color] peer py-3 outline outline-1 outline-gray-400 rounded-md peer placeholder:text-transparent focus-within:outline-2 focus-within:outline-black focus-within:placeholder:text-gray-400 aria-[invalid=true]:outline-2 aria-[invalid=true]:outline-rose-600 disabled:opacity-50 disabled:cursor-not-allowed focus:text-[#205FF3] focus:outline-[#205FF3]"}
        />
        <span
          className={
            `bottom-[22px] disabled:bg-transparent peer-focus:bottom-[45px]  
            flex absolute h-full items-center rounded-md py-0 px-1 mx-2 cursor-text transition-all  text-gray-400 outline-gray-400
            peer-focus:h-1   peer-focus:text-sm peer-focus:rounded-none peer-focus:text-[#205FF3] peer-focus:outline-black
            peer-[:not(:placeholder-shown)]:h-1   peer-[:not(:placeholder-shown)]:text-sm peer-[:not(:placeholder-shown)]:rounded-none
            peer-aria-[invalid=true]:text-roseoutline-[#CB5353] aria-[invalid=true]:peer-focus:text-[#CB5353]  aria-[invalid=true]:text-[#CB5353]
          `}
        >
          Username
        </span>
      </label>
    </div>
  );
}

How can I solve this problem?

You would need to look at applying the same styles that apply from peer-focus: variant classes. There are several ways you could approach this.

You could consider adding the required attribute to the <input>. This will mean that when the <input> has a value, the :valid CSS pseudo-class will match it, allowing you to use peer-valid::

const MyApp = () => {
  return(
    <div>
      <label className="relative flex min-h-10">
        <input
          className={
            "flex flex-1 p-2 bg-white disabled:bg-[#9b9b9b] text-black transition-[outline-color] peer py-3 outline outline-1 outline-gray-400 rounded-md peer placeholder:text-transparent focus-within:outline-2 focus-within:outline-black focus-within:placeholder:text-gray-400 aria-[invalid=true]:outline-2 aria-[invalid=true]:outline-rose-600 disabled:opacity-50 disabled:cursor-not-allowed focus:text-[#205FF3] focus:outline-[#205FF3]"}
          required
        />
        <span
          className={
            `bottom-[22px] disabled:bg-transparent peer-focus:bottom-[45px]
            flex absolute h-full items-center rounded-md py-0 px-1 mx-2 cursor-text transition-all  text-gray-400 outline-gray-400
            peer-focus:h-1   peer-focus:text-sm peer-focus:rounded-none peer-focus:text-[#205FF3] peer-focus:outline-black
            peer-[:not(:placeholder-shown)]:h-1   peer-[:not(:placeholder-shown)]:text-sm peer-[:not(:placeholder-shown)]:rounded-none
            peer-aria-[invalid=true]:text-roseoutline-[#CB5353] aria-[invalid=true]:peer-focus:text-[#CB5353]  aria-[invalid=true]:text-[#CB5353]
            peer-valid:h-1 peer-valid:text-sm peer-valid:rounded-none peer-valid:text-[#205FF3] peer-valid:outline-black peer-valid:bottom-[45px]
          `}
        >
          Username
        </span>
      </label>
    </div>
  );
}


ReactDOM.createRoot(document.getElementById('app')).render(<MyApp/>);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.production.min.js" integrity="sha512-8Q6Y9XnTbOE+JNvjBQwJ2H8S+UV4uA6hiRykhdtIyDYZ2TprdNmWOUaKdGzOhyr4dCyk287OejbPvwl7lrfqrQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.production.min.js" integrity="sha512-MOCpqoRoisCTwJ8vQQiciZv0qcpROCidek3GTFS6KTk2+y7munJIlKCVkFCYY+p3ErYFXCjmFjnfTTRSC1OHWQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://cdn.tailwindcss.com/3.4.1"></script>

<div id="app"></div>

You could also consider adding a placeholder attribute to the <input> this will mean that when the <input> has a value, the :placeholder-shown CSS pseudo-class will not match it, allowing you to use peer-[:not(:placeholder-shown)]::

const MyApp = () => {
  return(
    <div>
      <label className="relative flex min-h-10">
        <input
          className={
            "flex flex-1 p-2 bg-white disabled:bg-[#9b9b9b] text-black transition-[outline-color] peer py-3 outline outline-1 outline-gray-400 rounded-md peer placeholder:text-transparent focus-within:outline-2 focus-within:outline-black focus-within:placeholder:text-gray-400 aria-[invalid=true]:outline-2 aria-[invalid=true]:outline-rose-600 disabled:opacity-50 disabled:cursor-not-allowed focus:text-[#205FF3] focus:outline-[#205FF3]"}
            placeholder="foo"
        />
        <span
          className={
            `bottom-[22px] disabled:bg-transparent peer-focus:bottom-[45px]
            flex absolute h-1 items-center rounded-none py-0 px-1 mx-2 cursor-text transition-all  text-gray-400 outline-gray-400
            peer-focus:h-1   peer-focus:text-sm peer-focus:rounded-none peer-focus:text-[#205FF3] peer-focus:outline-black
            peer-aria-[invalid=true]:text-roseoutline-[#CB5353] aria-[invalid=true]:peer-focus:text-[#CB5353]  aria-[invalid=true]:text-[#CB5353] text-sm
            peer-[:not(:placeholder-shown)]:bottom-[45px] peer-[:not(:placeholder-shown)]:text-[#205FF3] peer-[:not(:placeholder-shown)]:outline-black
          `}
        >
          Username
        </span>
      </label>
    </div>
  );
}


ReactDOM.createRoot(document.getElementById('app')).render(<MyApp/>);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.production.min.js" integrity="sha512-8Q6Y9XnTbOE+JNvjBQwJ2H8S+UV4uA6hiRykhdtIyDYZ2TprdNmWOUaKdGzOhyr4dCyk287OejbPvwl7lrfqrQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.production.min.js" integrity="sha512-MOCpqoRoisCTwJ8vQQiciZv0qcpROCidek3GTFS6KTk2+y7munJIlKCVkFCYY+p3ErYFXCjmFjnfTTRSC1OHWQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://cdn.tailwindcss.com/3.4.1"></script>

<div id="app"></div>

You could also consider tracking if the <input> has a value with a React state, add applying classes conditionally:

const { useState } = React;

const MyApp = () => {
  const [hasValue, setHasValue] = useState();
  return(
    <div>
      <label className="relative flex min-h-10">
        <input
          className={
            "flex flex-1 p-2 bg-white disabled:bg-[#9b9b9b] text-black transition-[outline-color] peer py-3 outline outline-1 outline-gray-400 rounded-md peer placeholder:text-transparent focus-within:outline-2 focus-within:outline-black focus-within:placeholder:text-gray-400 aria-[invalid=true]:outline-2 aria-[invalid=true]:outline-rose-600 disabled:opacity-50 disabled:cursor-not-allowed focus:text-[#205FF3] focus:outline-[#205FF3]"}
          onChange={({ target }) => setHasValue(target.value !== '')}
        />
        <span
          className={
            `disabled:bg-transparent peer-focus:bottom-[45px] text-sm rounded-none
            flex h-1 absolute items-center py-0 px-1 mx-2 cursor-text transition-all
            peer-focus:h-1   peer-focus:text-sm peer-focus:rounded-none peer-focus:text-[#205FF3] peer-focus:outline-black
            peer-[:not(:placeholder-shown)]:h-1   peer-[:not(:placeholder-shown)]:text-sm peer-[:not(:placeholder-shown)]:rounded-none
            peer-aria-[invalid=true]:text-roseoutline-[#CB5353] aria-[invalid=true]:peer-focus:text-[#CB5353]  aria-[invalid=true]:text-[#CB5353]
            ${hasValue ? 'bottom-[45px] text-[#205FF3] outline-black' : 'bottom-[22px] text-gray-400 outline-gray-400'}
          `}
        >
          Username
        </span>
      </label>
    </div>
  );
}


ReactDOM.createRoot(document.getElementById('app')).render(<MyApp/>);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.production.min.js" integrity="sha512-8Q6Y9XnTbOE+JNvjBQwJ2H8S+UV4uA6hiRykhdtIyDYZ2TprdNmWOUaKdGzOhyr4dCyk287OejbPvwl7lrfqrQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.production.min.js" integrity="sha512-MOCpqoRoisCTwJ8vQQiciZv0qcpROCidek3GTFS6KTk2+y7munJIlKCVkFCYY+p3ErYFXCjmFjnfTTRSC1OHWQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://cdn.tailwindcss.com/3.4.1"></script>

<div id="app"></div>

Leave a Comment