Svi članci
How to11. pro 2024. · 7 min čitanja

How do you make a button that does not resize on load?

Learn how to prevent layout shifts in button loading states using grid stacking or absolute positioning, with expert insights from AS Agency.

Antonio ŠumakOsnivač, AS Agency
How do you make a button that does not resize on load?

The Problem 💡

Have you ever encountered a scenario where you needed to implement a loading state for a button, but clicking the button caused it to change size?

Button with layout shift on loading state
Button with layout shift on loading state

This behavior can look jittery, unprofessional, and not user-friendly. It also introduces layout shifts, which negatively impact your website’s user experience and SEO performance. So, how do you prevent this from happening? How should you approach this problem?

There are two main approaches to solving this issue, but one stands out as the better option. Let’s explore both.

  1. Absolute positioning approach
  2. Grid stacking approach

Initial approach

Initially, you might think to render text by default and, when the loading state is active, replace the text with a spinner. Here’s how you can create a basic button component:

TypeScript
import Spinner from './spinner';

type TProps = {
  text: string;
  isLoading?: boolean;
  onClick: () => void;
};
const Button = ({ text, isLoading, onClick }: TProps) => {
  return (
    <button
      onClick={onClick}
      className='px-4 py-2 bg-purple-500 text-white flex justify-between items-center'
    >
      {!isLoading ? <span>{text}</span> : <Spinner />}
    </button>
  );
};

export default Button;

As you can see, this is a straightforward button component with an isLoading property that determines whether the text or spinner is rendered.

TypeScript
'use client';

import Button from './components/button';
import { useState } from 'react';

export default function Home() {
  const [isLoading, setIsLoading] = useState(false);
  
  const handleLoading = () => {
    setIsLoading(true);
    setTimeout(() => {
      setIsLoading(false);
    }, 3000);
  };
  
  return (
    <main className='grid place-items-center h-screen'>
      <Button
        text='Submit this form'
        onClick={() => handleLoading()}
        isLoading={isLoading}
      />
    </main>
  );
}

Here’s how you would display the button component.

However, this approach leads to a resizing problem: when text is displayed, the button has a wider size, and when the loading state activates, the button becomes narrower. This is where we need to rethink our solution.

1. Absolute Positioning Approach 📊

To address the resizing issue, you can use absolute positioning. This requires a few adjustments inside the button and spinner components.

TypeScript
import Spinner from './spinner';

type TProps = {
  text: string;
  isLoading?: boolean;
  onClick: () => void;
};
const Button = ({ text, isLoading, onClick }: TProps) => {
  return (
    <button
      onClick={onClick}
      className='px-4 py-2 bg-purple-500 text-white flex justify-between items-center relative'
    >
      <span className={`${isLoading ? 'invisible' : 'visible'}`}>{text}</span>
      {isLoading && (
        <div className='absolute inset-0 w-full flex items-center justify-center'>
          <Spinner />
        </div>
      )}
    </button>
  );
};

export default Button;

The changes include adding the relative class to the button, modifying how button text is rendered during the loading state, and wrapping the Spinner component with an additional container.

But what’s the drawback of this approach? While it resolves the resizing issue, it introduces a new problem - text overflow.

Problem with a absolute approach
Problem with a absolute approach

As you can see, when you have a loading state message that is bigger than the initial button text, then you have a problem with text overflow. How can we possibly fix this problem?

2. Grid Stacking Approach 🌐

As shown here, if your loading state message is longer than the initial button text, it causes text overflow. To fix this, we need a better solution.

TypeScript
import Spinner from './spinner';

type TProps = {
  text: string;
  isLoading?: boolean;
  onClick: () => void;
};
const Button = ({ text, isLoading, onClick }: TProps) => {
  return (
    <button
      onClick={onClick}
      className={`px-4 py-2 bg-purple-500 text-white grid [grid-template-areas:stack]`}
    >
      <span
        className={`[grid-area:stack] ${!isLoading ? 'visible' : 'invisible'}`}
      >
        {text}
      </span>
      <Spinner
        className={`[grid-area:stack] place-self-center ${
          isLoading ? 'visible' : 'invisible'
        }`}
      />
    </button>
  );
};

export default Button;

First, you need to add grid as a className to the button, enabling CSS Grid layout. The key className here is [grid-template-areas:stack], which defines a single grid area called stack. This ensures all child elements stack on top of each other.

To make grid-areas work, apply [grid-area:stack] to both the button text and the Spinner component. Additionally, use classNames like visible and invisible to toggle the visibility of the elements as needed.

Grid stacking approach output
Grid stacking approach output

The spinner output remains identical to the absolute positioning approach. However, grid stacking offers a significant advantage.

Grid stacking approach with longer message
Grid stacking approach with longer message

As illustrated, the grid-based approach considers the largest child element when determining the button size. This means the button’s initial size already accommodates the longer loading message, preventing layout shifts.

Conclusion 🔧

This blog post has detailed two approaches for addressing button resizing issues during a loading state: absolute positioning and grid stacking. While absolute positioning offers a quick fix, grid stacking emerges as the superior solution by avoiding text overflow and layout shifts, thus improving user experience and maintaining SEO performance.

At AS Agency, we meticulously address these nuances to ensure our clients’ websites are polished, user-friendly, and optimized for the best performance. If you’re looking for a team that prioritizes every small detail to deliver exceptional results, look no further. Let’s create something amazing together! ✨

Napisao Antonio Šumak

Osnivač AS Agency. Dizajniram i izrađujem brze web stranice za male tvrtke — i zapisujem ono što naučim usput.

Podijeli

Rezervacije za list. / stu. 2026. · 2 slobodna mjesta

Spremni pokazati svijetusvoj posao?

Razgovarajmo o vašoj ideji na besplatnom, opuštenom pozivu od 30 minuta. Otići ćete s jasnim planom i poštenom ponudom.

Zagreb--:--New York--:--