htmlcssflexbox

Why does adding height: 100% cause a child to shrink, but removing it causes child to grow to fill space vertically?


I have a series of cards in a flex row. Some cards have longer titles, and therefore have a larger height. If I apply a height of 100% to all cards (using tailwind's h-full class), the smaller cards remain small. If I take that class away, the smaller cards grow to be 100% of the parent container's height, e.g. the height of the tallest card.

You can see a demo here: https://codesandbox.io/p/sandbox/wonderful-ptolemy-lyh7xh

<ul className="flex flex-col gap-4 items-stretch md:flex-row md:flex-wrap">
  <li className="border-2 rounded-lg p-2 w-80 h-full">...</li>
</ul>

const { useState } = React;

const DifficultyTypes = {
  Beginner: "Beginner",
  Intermediate: "Intermediate",
  Advanced: "Advanced",
}

const sessions = [{"id":"1","title":"Morning Meditation","instructor":"Sarah Johnson","date":"2023-07-15","time":"08:00 AM","duration":30,"difficultyLevel":"Beginner","description":"Start your day with a calming meditation session focused on mindfulness and presence."},{"id":"2","title":"Breathwork for Stress Relief waeghkfvslfhrfhrfg","instructor":"Michael Chen","date":"2023-07-16","time":"12:30 PM","duration":45,"difficultyLevel":"Intermediate","description":"Learn powerful breathing techniques to reduce stress and increase energy levels."},{"id":"3","title":"Advanced Visualization","instructor":"Emma Rodriguez","date":"2023-07-17","time":"05:00 PM","duration":60,"difficultyLevel":"Advanced","description":"Deepen your practice with advanced visualization techniques for personal growth."},{"id":"4","title":"Mindful Movement","instructor":"Sarah Johnson","date":"2023-07-18","time":"10:00 AM","duration":45,"difficultyLevel":"Beginner","description":"Combine gentle movement with mindfulness for an embodied meditation experience."},{"id":"5","title":"Focus and Confkor;gcentration","instructor":"David Kim","date":"2023-07-19","time":"02:00 PM","duration":30,"difficultyLevel":"Intermediate","description":"Strengthen your attention and focus with specialized concentration techniques."},{"id":"6","title":"Deep Relaxation","instructor":"Lisa Patel","date":"2023-07-20","time":"07:30 PM","duration":60,"difficultyLevel":"Beginner","description":"End your day with a deeply relaxing guided meditation for better sleep."},{"id":"7","title":"Mindfulness for Anxiety","instructor":"Michael Chen","date":"2023-07-21","time":"11:00 AM","duration":45,"difficultyLevel":"Intermediate","description":"Learn specific mindfulness practices designed to reduce anxiety and worry."},{"id":"8","title":"Transcendental Meditah;fher;ghj;rgtion","instructor":"Emma Rodriguez","date":"2023-07-22","time":"06:00 AM","duration":60,"difficultyLevel":"Advanced","description":"Experience the profound benefits of transcendental meditation techniques."}];

const Card = ({ session }) => {
  return (
    <li className="border-2 rounded-lg p-2 w-80 h-full">
      <h2 className="text-lg font-bold">{session.title}</h2>
      <p className="text-gray-500">{session.instructor}</p>
      <p>{session.difficultyLevel}</p>
      <div className="flex flex-row gap-2">
        <p>{session.date}</p>
        <p>{session.time}</p>
      </div>
    </li>
  );
};

function App() {
  const [sessionData, setSessionData] = useState(sessions);
  const [inputValue, setInputValue] = useState("");

  const handleSearch = (e) => {
    e.preventDefault();
    const filteredSessions = sessions.filter((session) =>
      session.title.toLowerCase().includes(inputValue.toLowerCase())
    );
    setSessionData(filteredSessions);
  };

  const handleChangeInput = (e) => {
    const searchValue = e.target.value;
    setInputValue(searchValue);
  };

  const handleSelectFilter = (e) => {
    const filterValue = e.target.value;
    console.log(filterValue, typeof filterValue);
    const filteredSessions = sessions.filter(
      (session) =>
        session.difficultyLevel.toLowerCase() === filterValue.toLowerCase()
    );
    setSessionData(filteredSessions);
  };

  return (
    <main className="min-h-screen p-8 flex flex-col gap-6">
      <h1 className="text-3xl font-bold">Mindfulness Sessions</h1>

      <div className="border-2 border-blue-500">
        <form className="flex flex-row gap-4">
          <input type="text" onChange={handleChangeInput} className="w-full" />
          <button type="submit" onClick={handleSearch}>
            Search
          </button>
        </form>
      </div>

      <div>
        <select onChange={handleSelectFilter} defaultValue="">
          <option value="" disabled>
            Select your option
          </option>
          <option value={DifficultyTypes.Beginner}>
            {DifficultyTypes.Beginner}
          </option>
          <option value={DifficultyTypes.Intermediate}>
            {DifficultyTypes.Intermediate}
          </option>
          <option value={DifficultyTypes.Advanced}>
            {DifficultyTypes.Advanced}
          </option>
        </select>
      </div>

      <div>
        <ul className="flex flex-col gap-4 items-stretch md:flex-row md:flex-wrap">
          {sessionData.map((session) => {
            return <Card key={session.id} session={session} />;
          })}
        </ul>
      </div>
    </main>
  );
}

ReactDOM
  .createRoot(root)
  .render(<App />);
.as-console-wrapper {
  display: none !important;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.3.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.3.1/umd/react-dom.production.min.js"></script>

<script src="https://unpkg.com/@tailwindcss/browser@4"></script>

<style type="text/tailwindcss">
@theme {
  --font-sans: var(--font-geist-sans);
  --font-mono: var(--font-geist-mono);
}

:root {
  --background: #ffffff;
  --foreground: #171717;
}

@media (prefers-color-scheme: dark) {
  :root {
    --background: #0a0a0a;
    --foreground: #ededed;
  }
}

body {
  color: var(--foreground);
  background: var(--background);
  font-family: var(--font-sans), Arial, Helvetica, sans-serif;
}

</style>

<div id="root"></div>

The styles are applied in the Card.tsx component. I currently have h-full applied, and the cards are varying heights. Remove that and the cards are all uniform.

What I want to understand is why this happens? Intuitively, I would expect height: 100% to fill the available height. I don't understand why this would instead make it shrink.


Solution

  • Why does adding height: 100% cause a child to shrink, but removing it causes child to grow to fill space vertically?

    Because height: 100% is computing to auto (content height) while overriding align-items: stretch. But when height: 100% is removed, align-items: stretch is free to work.


    Here's a more complete explanation of the problem.

    1. Percentage heights require a height specified on the parent.

      The CSS height property, when used with a percentage value, is calculated with respect to the element's containing block. If the height of the containing block is not specified (and the element is not absolutely positioned), then the value computes to auto, meaning the height of the content.

      So your cards (li) have height: 100% applied. But there's no height specified on the containing block (the parent ul). So height: 100% isn't doing anything and all cards are just the height of the content. That's what you're seeing.

      For illustration purposes, add ul { height: 250px } to your code. Now height: 100% is working. All cards have equal height.

      For a more detailed explanation see my answer here:


    1. The height property overrides align-items: stretch.

      With align-items: stretch in your code, the flex items will expand across all available space in the cross axis. Except the height property effectively overrides this setting.

      For illustration purposes, remove the height: 100% from the flex items (li). Now align-items: stretch works. All cards have full and equal height.

      You can also try align-items: flex-start without height: 100%. This will emulate the rendering of your original code (as it packs the items to fit their content).