Menu

Tailwind CSS Transform Origin

The transform-origin property specifies the point around which a transformation (like rotate, scale, or skew) occurs. By default, this origin is set to the center of an element (50% 50%). This functionality is crucial for animations and transformations to behave precisely.

Tailwind CSS offers a comprehensive set of utilities to handle transform-origin. It also lets you configure the default theme, ensuring you can define both default and custom origin points. Let's explore how to use these utilities in-depth.

ClassPropertiesExample
origin-centertransform-origin: center;<div className="origin-center"></div>
origin-toptransform-origin: top;<div className="origin-top"></div>
origin-top-righttransform-origin: top right;<div className="origin-top-right"></div>
origin-righttransform-origin: right;<div className="origin-right"></div>
origin-bottom-righttransform-origin: bottom right;<div className="origin-bottom-right"></div>
origin-bottomtransform-origin: bottom;<div className="origin-bottom"></div>
origin-bottom-lefttransform-origin: bottom left;<div className="origin-bottom-left"></div>
origin-lefttransform-origin: left;<div className="origin-left"></div>
origin-top-lefttransform-origin: top left;<div className="origin-top-left"></div>

Overview of Transform Origin

Adding the Transform Origin

Tailwind CSS provides predefined utilities for common transform-origin points. You can align transformations to places like the top-left, center, bottom-right, etc. This small yet powerful feature comes handy when building animations or transitions tied to specific parts of an element.

In the below example, each scaled image applies a different transform-origin utility.

This is a live editor. Play around with it!
export default function TransformOriginDemo() {
  return (
    <div className="flex gap-2 items-center justify-center h-screen w-screen">
      {/* Top-left */}
      <div className="relative bg-gray-200">
        <img
          src="https://images.unsplash.com/photo-1705909237050-7a7625b47fac"
          alt="Sample"
          className="scale-75 origin-top-left h-32 w-60 object-cover"
        />
        <span className="absolute mt-2 text-center text-sm text-gray-700">
          origin-top-left
        </span>
      </div>

      {/* Center */}
      <div className="relative bg-gray-200">
        <img
          src="https://images.unsplash.com/photo-1705909237050-7a7625b47fac"
          alt="Sample"
          className="scale-75 origin-center h-32 w-60 object-cover"
        />
        <span className="absolute mt-2 text-center text-sm text-gray-700">
          origin-center
        </span>
      </div>

      {/* Bottom-right */}
      <div className="relative bg-gray-200">
        <img
          src="https://images.unsplash.com/photo-1705909237050-7a7625b47fac"
          alt="Sample"
          className="scale-75 origin-bottom-right h-32 w-60 object-cover"
        />
        <span className="absolute mt-2 text-center text-sm text-gray-700">
          origin-bottom-right
        </span>
      </div>
    </div>
  );
}

States and Responsiveness

Tailwind also lets you conditionally apply transform-origin utilities. You can modify transform-origin based on certain states or breakpoints, enabling scenarios like hover effects or responsive orientations.

Hover and Focus States

By using Tailwind's state modifiers, you can modify an element’s transform-origin when it's hovered over or focused, e.g., hover:origin-top, hover:origin-bottom.

The below image starts at the center and changes its transform-origin to origin-top-right when scaled on hover.

This is a live editor. Play around with it!
export default function HoverTransformOrigin() {
  return (
    <div className="flex items-center justify-center h-screen w-screen bg-gray-100">
      <img
        src="https://images.unsplash.com/photo-1705909237050-7a7625b47fac"
        alt="Hover Origin"
        className="h-48 w-64 transform scale-90 origin-center hover:origin-top-right hover:scale-110 transition-transform duration-300"
      />
    </div>
  );
}

Breakpoint Modifiers

Tailwind’s breakpoint modifiers let you adjust transform-origin based on the device's width. Use modifiers such as sm, md, lg, etc. to apply certain transform-origin utilities on and above the specified breakpoints.

The below image changes its transform-origin to origin-bottom-left on md breakpoint and origin-top-right on lg breakpoint.

This is a live editor. Play around with it!
export default function ResponsiveTransformOrigin() {
  return (
    <div className="flex items-center justify-center h-screen bg-gray-100">
      <img
        src="https://images.unsplash.com/photo-1705909237050-7a7625b47fac"
        alt="Responsive Origin"
        className="h-48 w-64 transform md:scale-110 md:origin-bottom-left lg:scale-125 lg:origin-top-right"
      />
    </div>
  );
}

Custom Transform Origin

Sometimes the default utilities might not suffice, especially for unique designs or animations. In such cases, Tailwind allows you to configure your theme or directly apply arbitrary values for transform-origin.

Extending the Theme

Tailwind’s configuration file (tailwind.config.js) is incredibly flexible. You can add new transform-origin values, making them reusable throughout your project. For example, let’s extend the transformOrigin theme to declare custom top-center and bottom-center values:

This is a live editor. Play around with it!
import tailwindConfig from "./tailwind.config.js";
tailwind.config = tailwindConfig;

export default function CustomThemeOrigin() {
  return (
    <div className="flex items-center justify-center h-screen w-screen bg-gray-50">
      <img
        src="https://images.unsplash.com/photo-1705909237050-7a7625b47fac"
        alt="Custom Theme Origin"
        className="h-56 w-64 transform scale-90 origin-top-center"
      />
    </div>
  );
}

Using Arbitrary Values

For one-off cases, where you don't want to configure the theme to use a custom utility, Tailwind allows for arbitrary values. You can use arbitrary values on the fly by defining the position in square brackets.

This is a live editor. Play around with it!
export default function ArbitraryTransformOrigin() {
  return (
    <div className="flex items-center justify-center h-screen w-screen bg-gray-100">
      <img
        src="https://images.unsplash.com/photo-1705909237050-7a7625b47fac"
        alt="Arbitrary Origin"
        className="h-48 w-64 transform scale-110 origin-[25%_75%]"
      />
    </div>
  );
}

Real World Examples

Notification Panel

This examples shows a notification panel where alerts slide in from different origins based on their priority.

This is a live editor. Play around with it!
const NotificationPanel = () => {
  const notifications = [
    {
      id: 1,
      type: "urgent",
      title: "System Update Required",
      message: "Critical security update available. Please restart your system.",
      time: "2 minutes ago",
      origin: "origin-top-right",
      color: "bg-red-100 border-red-500"
    },
    {
      id: 2,
      type: "message",
      title: "New Message",
      message: "Sarah sent you a project proposal",
      icon: "https://images.unsplash.com/photo-1611746872915-64382b5c76dl",
      time: "5 minutes ago",
      origin: "origin-left",
      color: "bg-blue-100 border-blue-500"
    },
    {
      id: 3,
      type: "reminder",
      title: "Meeting Reminder",
      message: "Team standup in 15 minutes",
      time: "10 minutes ago",
      origin: "origin-bottom-left",
      color: "bg-yellow-100 border-yellow-500"
    },
    {
      id: 4,
      type: "success",
      title: "Deploy Successful",
      message: "Latest version deployed to production",
      time: "15 minutes ago",
      origin: "origin-right",
      color: "bg-green-100 border-green-500"
    },
    {
      id: 5,
      type: "alert",
      title: "Storage Warning",
      message: "You're running low on storage space",
      time: "20 minutes ago",
      origin: "origin-top",
      color: "bg-orange-100 border-orange-500"
    },
    {
      id: 6,
      type: "info",
      title: "New Feature Available",
      message: "Check out our latest productivity tools",
      time: "25 minutes ago",
      origin: "origin-bottom-right",
      color: "bg-purple-100 border-purple-500"
    }
  ];

  return (
    <div className="max-w-md mx-auto p-6">
      <h2 className="text-2xl font-bold mb-6">Notifications</h2>
      <div className="space-y-4">
        {notifications.map((notification) => (
          <div
            key={notification.id}
            className={`group relative border-l-4 p-4 rounded-r-lg shadow-sm 
              transform transition-all duration-300 hover:scale-105 
              ${notification.color} ${notification.origin}`}
          >
            <div className="flex items-start">
              <div className="ml-3 flex-1">
                <div className="flex items-center justify-between">
                  <p className="text-sm font-medium">{notification.title}</p>
                  <span className="text-xs text-gray-500">{notification.time}</span>
                </div>
                <p className="mt-1 text-sm text-gray-600">{notification.message}</p>
              </div>
            </div>
            <div className="absolute inset-y-0 right-0 pr-4 flex items-center opacity-0 group-hover:opacity-100 transition-opacity">
              <button className="text-gray-400 hover:text-gray-500">
                <span className="sr-only">Close</span></button>
            </div>
            <div className="absolute -right-2 top-1/2 transform -translate-y-1/2 opacity-0 group-hover:opacity-100 transition-opacity">
              <div className="bg-white shadow-lg rounded-lg p-2">
                <button className="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 rounded">
                  Mark as read
                </button>
                <button className="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 rounded">
                  Snooze
                </button>
              </div>
            </div>
          </div>
        ))}
      </div>
    </div>
  );
};

export default NotificationPanel;

Card Stack Animation

This example demonstrates a stack of social media cards that rotate from different origin points when hovered.

This is a live editor. Play around with it!
const SocialCardStack = () => {
  const cards = [
    {
      id: 1,
      author: "Emma Thompson",
      role: "Travel Photographer",
      content: "Captured the most amazing sunset in Bali today!",
      likes: 1234,
      image: "https://images.unsplash.com/photo-1573496359142-b8d87734a5a2",
      originPoint: "origin-top-left"
    },
    {
      id: 2,
      author: "James Wilson",
      role: "Food Critic",
      content: "This hidden gem in Tokyo serves the best ramen!",
      likes: 856,
      image: "https://images.unsplash.com/photo-1629425733761-caae3b5f2e50",
      originPoint: "origin-top-right"
    },
    {
      id: 3,
      author: "Sarah Chen",
      role: "Digital Artist",
      content: "Just finished my latest digital artwork series!",
      likes: 2431,
      image: "https://images.unsplash.com/photo-1551836022-deb4988cc6c0",
      originPoint: "origin-bottom-left"
    },
    {
      id: 4,
      author: "Michael Rodriguez",
      role: "Street Photographer",
      content: "Streets of New York never cease to amaze me.",
      likes: 1567,
      image: "https://images.unsplash.com/photo-1500648767791-00dcc994a43e",
      originPoint: "origin-bottom-right"
    },
    {
      id: 5,
      author: "Lisa Park",
      role: "Adventure Blogger",
      content: "Hiking the Himalayas was life-changing!",
      likes: 3298,
      image: " https://images.unsplash.com/photo-1609436132311-e4b0c9370469",
      originPoint: "origin-center"
    },
    {
      id: 6,
      author: "Alex Kumar",
      role: "Tech Reviewer",
      content: "Hands-on with the latest smartphone innovations!",
      likes: 945,
      image: "https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d",
      originPoint: "origin-top"
    }
  ];

  return (
    <div className="flex items-center justify-center min-h-screen bg-gray-100 p-8">
      <div className="grid grid-cols-2 gap-6 max-w-4xl">
        {cards.map((card) => (
          <div
            key={card.id}
            className={`group relative bg-white rounded-lg shadow-lg overflow-hidden 
              hover:rotate-6 transition-transform duration-300 ${card.originPoint}`}
          >
            <img
              src={card.image}
              alt={`Posted by ${card.author}`}
              className="w-full h-48 object-cover"
            />
            <div className="p-4">
              <div className="flex flex-col mb-2 gap-2">
                <h3 className="font-bold text-lg">{card.author}</h3>
                <span className=" text-sm text-gray-500">{card.role}</span>
              </div>
              <p className="text-gray-700 my-5">{card.content}</p>
              <div className="flex items-center text-gray-500">
                <span>❤️ {card.likes}</span>
              </div>
            </div>
          </div>
        ))}
      </div>
    </div>
  );
};

export default SocialCardStack;

Product Showcase

This component creates an interactive product showcase where items rotate from different origins on hover.

This is a live editor. Play around with it!
const ProductShowcase = () => {
  const products = [
    {
      id: 1,
      name: "Minimalist Watch",
      price: 199.99,
      image: "https://images.unsplash.com/photo-1523275335684-37898b6baf30",
      category: "Accessories",
      rating: 4.8,
      originPoint: "origin-left"
    },
    {
      id: 2,
      name: "Leather Backpack",
      price: 149.99,
      image: "https://images.unsplash.com/photo-1553062407-98eeb64c6a62",
      category: "Bags",
      rating: 4.6,
      originPoint: "origin-right"
    },
    {
      id: 3,
      name: "Wireless Headphones",
      price: 299.99,
      image: "https://images.unsplash.com/photo-1505740420928-5e560c06d30e",
      category: "Electronics",
      rating: 4.9,
      originPoint: "origin-top"
    },
    {
      id: 4,
      name: "Running Shoes",
      price: 129.99,
      image: "https://images.unsplash.com/photo-1542291026-7eec264c27ff",
      category: "Footwear",
      rating: 4.7,
      originPoint: "origin-bottom"
    },
    {
      id: 5,
      name: "Sunglasses",
      price: 89.99,
      image: "https://images.unsplash.com/photo-1572635196237-14b3f281503f",
      category: "Accessories",
      rating: 4.5,
      originPoint: "origin-center"
    },
    {
      id: 6,
      name: "Smart Watch",
      price: 399.99,
      image: "https://images.unsplash.com/photo-1546868871-7041f2a55e12",
      category: "Electronics",
      rating: 4.8,
      originPoint: "origin-top-right"
    }
  ];

  return (
    <div className="min-h-screen bg-gray-50 py-12 px-4">
      <div className="max-w-6xl mx-auto">
        <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
          {products.map((product) => (
            <div
              key={product.id}
              className={`group relative bg-white rounded-xl shadow-md overflow-hidden
                hover:scale-105 hover:rotate-3 transition-all duration-300 ${product.originPoint}`}
            >
              <div className="aspect-w-1 aspect-h-1">
                <img
                  src={product.image}
                  alt={product.name}
                  className="w-full h-64 object-cover"
                />
              </div>
              <div className="p-6">
                <span className="text-sm text-indigo-600 font-medium">
                  {product.category}
                </span>
                <h3 className="mt-2 text-xl font-semibold text-gray-900">
                  {product.name}
                </h3>
                <div className="mt-2 flex items-center">
                  {"⭐".repeat(Math.floor(product.rating))}
                  <span className="ml-1 text-sm text-gray-500">
                    ({product.rating})
                  </span>
                </div>
                <p className="mt-3 text-2xl font-bold text-gray-900">
                  ${product.price}
                </p>
                <button className="mt-4 w-full bg-indigo-600 text-white py-2 px-4 rounded-lg
                  hover:bg-indigo-700 transition-colors duration-200">
                  Add to Cart
                </button>
              </div>
            </div>
          ))}
        </div>
      </div>
    </div>
  );
};

export default ProductShowcase;

Team Member Directory

This component displays team members with rotating profile cards that pivot from different origins.

This is a live editor. Play around with it!
const TeamDirectory = () => {
  const teamMembers = [
    {
      id: 1,
      name: "Dr. Alexandra Chen",
      role: "Chief Medical Officer",
      image: "https://images.unsplash.com/photo-1438761681033-6461ffad8d80",
      specialty: "Neurosurgery",
      experience: "15+ years",
      education: "Harvard Medical School",
      originPoint: "origin-top-left"
    },
    {
      id: 2,
      name: "Dr. Marcus Rodriguez",
      role: "Head of Cardiology",
      image: "https://images.unsplash.com/photo-1472099645785-5658abf4ff4e",
      specialty: "Cardiology",
      experience: "12 years",
      education: "Johns Hopkins University",
      originPoint: "origin-top-right"
    },
    {
      id: 3,
      name: "Dr. Sarah Williams",
      role: "Lead Pediatrician",
      image: "https://images.unsplash.com/photo-1494790108377-be9c29b29330",
      specialty: "Pediatrics",
      experience: "10 years",
      education: "Stanford Medical School",
      originPoint: "origin-bottom-left"
    },
    {
      id: 4,
      name: "Dr. James Thompson",
      role: "Orthopedic Surgeon",
      image: "https://images.unsplash.com/photo-1500648767791-00dcc994a43e",
      specialty: "Orthopedics",
      experience: "8 years",
      education: "Yale School of Medicine",
      originPoint: "origin-bottom-right"
    },
    {
      id: 5,
      name: "Dr. Emily Parker",
      role: "Oncologist",
      image: "https://images.unsplash.com/photo-1573497019940-1c28c88b4f3e",
      specialty: "Oncology",
      experience: "14 years",
      education: "UCLA Medical Center",
      originPoint: "origin-center"
    },
    {
      id: 6,
      name: "Dr. Michael Chang",
      role: "Dermatologist",
      image: "https://images.unsplash.com/photo-1537368910025-700350fe46c7",
      specialty: "Dermatology",
      experience: "9 years",
      education: "Columbia University",
      originPoint: "origin-top"
    }
  ];

  return (
    <div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100 py-16 px-4">
      <div className="max-w-7xl mx-auto">
        <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-10">
          {teamMembers.map((member) => (
            <div
              key={member.id}
              className={`group relative bg-white rounded-2xl shadow-xl overflow-hidden
                hover:rotate-2 transition-all duration-500 ${member.originPoint}`}
            >
              <div className="absolute inset-0 bg-gradient-to-t from-black/60 to-transparent z-10"/>
              <img
                src={member.image}
                alt={member.name}
                className="w-full h-96 object-cover object-center"
              />
              <div className="absolute bottom-0 left-0 right-0 p-6 text-white z-20">
                <h3 className="text-2xl font-bold">{member.name}</h3>
                <p className="text-lg font-medium text-blue-200">{member.role}</p>
                <div className="mt-4 space-y-2 opacity-0 group-hover:opacity-100 transition-opacity duration-300">
                  <p className="flex items-center">
                    <span className="font-semibold mr-2">Specialty:</span>
                    {member.specialty}
                  </p>
                  <p className="flex items-center">
                    <span className="font-semibold mr-2">Experience:</span>
                    {member.experience}
                  </p>
                  <p className="flex items-center">
                    <span className="font-semibold mr-2">Education:</span>
                    {member.education}
                  </p>
                </div>
              </div>
            </div>
          ))}
        </div>
      </div>
    </div>
  );
};

export default TeamDirectory;

This component showcases recipe cards with rotating animations from different origin points.

This is a live editor. Play around with it!
const RecipeGallery = () => {
  const recipes = [
    {
      id: 1,
      name: "Mediterranean Quinoa Bowl",
      chef: "Isabella Martinez",
      prepTime: "25 mins",
      difficulty: "Medium",
      calories: 450,
      image: "https://images.unsplash.com/photo-1512621776951-a57141f2eefd",
      category: "Healthy",
      originPoint: "origin-top-left"
    },
    {
      id: 2,
      name: "Classic Beef Wellington",
      chef: "Gordon Mitchell",
      prepTime: "120 mins",
      difficulty: "Advanced",
      calories: 850,
      image: "https://images.unsplash.com/photo-1544025162-d76694265947",
      category: "Gourmet",
      originPoint: "origin-top-right"
    },
    {
      id: 3,
      name: "Vegan Sushi Rolls",
      chef: "Yuki Tanaka",
      prepTime: "45 mins",
      difficulty: "Medium",
      calories: 320,
      image: "https://images.unsplash.com/photo-1579584425555-c3ce17fd4351",
      category: "Vegan",
      originPoint: "origin-center"
    },
    {
      id: 4,
      name: "Spicy Thai Curry",
      chef: "Somchai Patel",
      prepTime: "35 mins",
      difficulty: "Easy",
      calories: 580,
      image: "https://images.unsplash.com/photo-1455619452474-d2be8b1e70cd",
      category: "Asian",
      originPoint: "origin-bottom-left"
    },
    {
      id: 5,
      name: "Artisan Sourdough Bread",
      chef: "Marie Dubois",
      prepTime: "180 mins",
      difficulty: "Advanced",
      calories: 220,
      image: "https://images.unsplash.com/photo-1509440159596-0249088772ff",
      category: "Baking",
      originPoint: "origin-bottom-right"
    },
    {
      id: 6,
      name: "Rainbow Smoothie Bowl",
      chef: "Sarah Wilson",
      prepTime: "15 mins",
      difficulty: "Easy",
      calories: 380,
      image: "https://images.unsplash.com/photo-1511690743698-d9d85f2fbf38",
      category: "Breakfast",
      originPoint: "origin-top"
    }
  ];

  return (
    <div className="min-h-screen bg-gradient-to-r from-orange-50 to-rose-50 py-12 px-4">
      <div className="max-w-7xl mx-auto">
        <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
          {recipes.map((recipe) => (
            <div
              key={recipe.id}
              className={`group relative bg-white rounded-xl shadow-lg overflow-hidden
                hover:scale-105 hover:-rotate-3 transition-all duration-300 ${recipe.originPoint}`}
            >
              <div className="relative h-64">
                <img
                  src={recipe.image}
                  alt={recipe.name}
                  className="w-full h-full object-cover"
                />
                <div className="absolute top-4 right-4 bg-white px-3 py-1 rounded-full text-sm font-medium text-rose-600">
                  {recipe.category}
                </div>
              </div>
              <div className="p-6">
                <h3 className="text-xl font-bold text-gray-900">{recipe.name}</h3>
                <p className="mt-1 text-gray-600">by {recipe.chef}</p>
                <div className="mt-4 flex flex-wrap gap-4">
                  <div className="flex items-center">
                    <span className="text-gray-500">⏱️</span>
                    <span className="ml-1 text-sm text-gray-600">{recipe.prepTime}</span>
                  </div>
                  <div className="flex items-center">
                    <span className="text-gray-500">🔥</span>
                    <span className="ml-1 text-sm text-gray-600">{recipe.difficulty}</span>
                  </div>
                  <div className="flex items-center">
                    <span className="text-gray-500">📊</span>
                    <span className="ml-1 text-sm text-gray-600">{recipe.calories} cal</span>
                  </div>
                </div>
                <button className="mt-6 w-full bg-rose-600 text-white py-2 px-4 rounded-lg
                  hover:bg-rose-700 transition-colors duration-200">
                  View Recipe
                </button>
              </div>
            </div>
          ))}
        </div>
      </div>
    </div>
  );
};

export default RecipeGallery;

Customization Examples

This example showcases a grid of cards that rotate from different origins when hovered.

This is a live editor. Play around with it!
// App.js
import tailwindConfig from "./tailwind.config.js";
tailwind.config = tailwindConfig;

const RotatingCardGallery = () => {
  const cards = [
    {
      title: "Mountain Vista",
      image: "https://images.unsplash.com/photo-1519681393784-d120267933ba",
      origin: "origin-top-right-corner",
    },
    {
      title: "Ocean Sunset",
      image: "https://images.unsplash.com/photo-1518837695005-2083093ee35b",
      origin: "origin-diagonal",
    },
    {
      title: "Forest Path",
      image: "https://images.unsplash.com/photo-1441974231531-c6227db76b6e",
      origin: "origin-center",
    },
  ];

  return (
    <div className="w-full h-screen overflow-x-auto snap-x snap-mandatory">
      <div className="flex">
        {cards.map((card, index) => (
          <div key={index} className="snap-start flex-shrink-0 w-80 h-96 m-4">
            <div 
              className={`relative w-full h-full rounded-xl shadow-2xl transition-transform duration-500 hover:rotate-12 ${card.origin}`}
            >
              <img
                src={card.image}
                alt={card.title}
                className="w-full h-full object-cover rounded-xl"
              />  
              <h3 className="absolute bottom-4 left-4 text-white text-xl font-bold bg-black bg-opacity-50 px-2 py-1 rounded">
                {card.title}
              </h3>
            </div>
          </div>
        ))}
      </div>
    </div>
  );
};

export default RotatingCardGallery;

Diagonal Corner Expansion

This example shows a button that expands from its top-right corner using a custom transform origin.

This is a live editor. Play around with it!
// App.js
import tailwindConfig from "./tailwind.config.js";
tailwind.config = tailwindConfig;

export default function DiagonalButton() {
  return (
    <div className="flex h-screen items-center justify-center bg-gray-900">
      <button 
        className="relative overflow-hidden px-8 py-4 bg-transparent border-2 border-emerald-400 text-emerald-400 rounded-lg group"
      >
        <span className="relative z-10">Hover Me</span>
        <div 
          className="absolute inset-0 bg-emerald-400 origin-corner scale-0 transition-transform duration-500 ease-out group-hover:scale-100"
        />
        <span className="absolute inset-0 z-10 flex items-center justify-center text-transparent group-hover:text-gray-900 transition-colors duration-500">
          Transformed!
        </span>
      </button>
    </div>
  )
}

This example shows a vertical scrolling carousel with text that transforms from custom origins.

This is a live editor. Play around with it!
// App.js
import tailwindConfig from "./tailwind.config.js";
tailwind.config = tailwindConfig;

const PerspectiveTextCarousel = () => {
  const texts = [
    "Innovation",
    "Creativity",
    "Design",
    "Technology"
  ];

  return (
    <div className="h-screen w-screen overflow-y-scroll snap-y snap-mandatory">
      {texts.map((text, index) => (
        <div 
          key={index}
          className="h-screen w-[800px] flex items-center snap-start bg-gradient-to-r from-indigo-500 to-purple-500"
        >
          <h1 
            className="text-8xl font-bold text-white px-24 transition-transform 
                       duration-700 origin-text-edge hover:translate-x-12 
                       hover:scale-110"
          >
            {text}
          </h1>
        </div>
      ))}
    </div>
  );
};

export default PerspectiveTextCarousel;

Best Practices

Maintain Design Consistency

Ensuring a uniform visual experience is crucial when using Tailwind’s origin-* utilities. Consistent transform origins prevent abrupt shifts in animation and scaling behaviors across components. Establishing a design system that standardizes transform origins for elements such as buttons, cards, and modal windows helps in maintaining predictable motion patterns.

When designing components, align transform origins with content positioning. For example, elements positioned on the left should use origin-left, while centered elements should use origin-center. This maintains alignment integrity across the UI. A structured approach like this reduces inconsistencies, especially when animations are involved.

Balance with Other Layout Properties

A well-structured UI requires balancing origin-* utilities with spacing, flexbox, and grid properties. When misaligned, transform origins can lead to visual distortions or elements shifting unexpectedly within their parent containers.

For instance, when using flex or grid, ensure that transform origins align with content direction. A flex-based navigation bar should use origin-left for left-aligned elements to maintain structural integrity. Similarly, grid layouts should match origin-center for scalable elements placed in the middle.

Ensuring transform origins do not conflict with spacing (gap-*, m-*, p-*) or alignment (justify-*, items-*) properties is essential. Maintaining this balance prevents layout inconsistencies and keeps UI components structured and predictable.

Accessibility Considerations

Enhance Readability and Navigability

Transform origin can influence how users perceive content hierarchy and navigation flow. When used in animations, improper transform origins can create disorienting effects that hinder readability.

To ensure a smooth experience, avoid erratic scaling or rotation that hampers text legibility. For example, a modal expanding from origin-top-left may disrupt focus order, while a origin-center ensures content remains easily trackable.

For interactive elements, ensure transformations do not introduce unintended shifts that make navigation difficult for users relying on the keyboard inputs.

Focus on High Contrast

Transformations can sometimes impact contrast perception, especially when applied to interactive elements. For example, scaling effects that reduce an element's apparent size may make it harder to distinguish it from its background.

For elements with dynamic transformations, maintain sufficient contrast ratios using Tailwind’s bg-opacity-* and opacity-* utilities. Pairing transform origin with contrast-enhancing utilities ensures visibility remains optimal.