Skip to main content
Velaria uses two powerful animation libraries to create smooth, performant animations throughout the site:
  • tailwindcss-animated for pre-built animation classes
  • tailwindcss-intersect for scroll-triggered animations
These tools work together to create engaging visual experiences without writing custom CSS or JavaScript.

Animation libraries

Both libraries are installed via npm:
package.json
{
  "dependencies": {
    "tailwindcss-animated": "^2.0.0",
    "tailwindcss-intersect": "^2.2.0"
  }
}
These libraries extend Tailwind CSS with new utility classes. No additional configuration is required.

Intersection observer setup

The tailwindcss-intersect plugin requires a script to observe elements:
src/layouts/Layout.astro
<script 
  src="https://unpkg.com/tailwindcss-intersect@2.x.x/dist/observer.min.js"
></script>
Location: src/layouts/Layout.astro:31
This script must be loaded before any components use intersect: classes. Place it in your main layout file to ensure it’s available on all pages.

How it works

1

Observer initialization

The script creates an Intersection Observer that watches for elements with intersect: classes.
2

Element detection

When an element enters the viewport, the observer detects it.
3

Class application

The intersect: classes are applied, triggering animations.
4

One-time execution

By default, animations only trigger once per page load.

Available animation classes

Fade animations

animate-fade         /* Fade in */
animate-fade-up      /* Fade in from bottom */
animate-fade-down    /* Fade in from top */
animate-fade-left    /* Fade in from right */
animate-fade-right   /* Fade in from left */

Scale animations

animate-scale-up     /* Scale up from 0 to 100% */
animate-scale-down   /* Scale down */

Slide animations

animate-slide-up     /* Slide up */
animate-slide-down   /* Slide down */
animate-slide-left   /* Slide left */
animate-slide-right  /* Slide right */

Specialized animations

animate-bounce       /* Bounce effect */
animate-spin         /* Continuous rotation */
animate-ping         /* Expanding circle effect */
animate-pulse        /* Pulsing scale effect */
View all available animations in the tailwindcss-animated documentation.

Animation modifiers

Duration

Control how long animations take:
animate-duration-[1000ms]   /* 1 second */
animate-duration-[500ms]    /* 0.5 seconds */
animate-duration-[2000ms]   /* 2 seconds */

Delay

Add delays before animations start:
delay-200    /* 200ms delay */
delay-300    /* 300ms delay */
delay-500    /* 500ms delay */

Repeat

animate-once      /* Play once (default) */
animate-infinite  /* Loop forever */

Easing

animate-ease-in       /* Slow start */
animate-ease-out      /* Slow end */
animate-ease-in-out   /* Slow start and end */

Real-world examples

Product catalog animations

Each product card scales up and fades in with a staggered delay:
src/components/Catalogo.astro
<!-- First product: no delay -->
<div class="group scale-50 opacity-0 intersect:scale-100 intersect:opacity-100 transition duration-500">
  <!-- Product content -->
</div>

<!-- Second product: 200ms delay -->
<div class="group scale-50 opacity-0 intersect:scale-100 intersect:opacity-100 transition delay-200 duration-600">
  <!-- Product content -->
</div>

<!-- Third product: 300ms delay -->
<div class="group scale-50 opacity-0 intersect:scale-100 intersect:opacity-100 transition delay-300 duration-700">
  <!-- Product content -->
</div>

<!-- Fourth product: 400ms delay -->
<div class="group scale-50 opacity-0 intersect:scale-100 intersect:opacity-100 transition delay-400 duration-800">
  <!-- Product content -->
</div>

<!-- Remaining products: 500ms delay -->
<div class="group scale-50 opacity-0 intersect:scale-100 intersect:opacity-100 transition delay-500 duration-900">
  <!-- Product content -->
</div>
See it in action: src/components/Catalogo.astro:14-127
1

Initial state

  • scale-50: Product starts at 50% size
  • opacity-0: Completely transparent
2

Intersection trigger

When the element enters viewport:
  • intersect:scale-100: Scales to full size
  • intersect:opacity-100: Fades to fully visible
3

Transition properties

  • transition: Enables smooth animation
  • delay-X: Staggers each product’s animation
  • duration-X: Each product animates slightly longer

Fragrances fade-up

The fragrances section uses a pre-built fade-up animation:
src/components/Fragancias.astro
<div 
  class="flex flex-col justify-center items-center 
         intersect:animate-fade-up 
         animate-once 
         animate-duration-[1000ms] 
         animate-ease-in 
         my-20"
>
  <div class="w-[80%] lg:w-[50%]">
    <h2 class="text-center text-3xl mb-10">Fragancias disponibles</h2>
    <ul>
      <li class="mb-5 text-2xl text-orange-900 text-center italic">Vainilla</li>
      <!-- More fragrances... -->
    </ul>
  </div>
</div>
See it in action: src/components/Fragancias.astro:2
  • intersect:animate-fade-up: Fades in from below when visible
  • animate-once: Only animates once (no repeat on re-scroll)
  • animate-duration-[1000ms]: 1 second animation
  • animate-ease-in: Smooth acceleration

ScrollToTop component

The “back to top” button uses vanilla JavaScript for smooth scrolling:
src/components/ScrollToTop.astro
<button
  type="button"
  class="!fixed bottom-5 end-5 hidden rounded-full bg-amber-600 
         p-4 text-xs font-medium uppercase leading-tight text-white 
         shadow-md transition duration-150 ease-in-out 
         hover:bg-amber-700 hover:shadow-lg 
         focus:bg-amber-700 focus:shadow-lg focus:outline-none focus:ring-0 
         active:bg-amber-800 active:shadow-lg"
  id="btn-back-to-top"
>
  <span class="[&>svg]:w-6">
    <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" 
         stroke-width="3" stroke="currentColor">
      <path stroke-linecap="round" stroke-linejoin="round" 
            d="M4.5 10.5 12 3m0 0 7.5 7.5M12 3v18" />
    </svg>
  </span>
</button>
See it in action: src/components/ScrollToTop.astro:1-21

JavaScript implementation

The button’s behavior is controlled in the main layout:
src/layouts/Layout.astro
const mybutton = document.getElementById("btn-back-to-top");

const scrollFunction = () => {
  if (
    document.body.scrollTop > 20 ||
    document.documentElement.scrollTop > 20
  ) {
    mybutton.classList.remove("hidden");
  } else {
    mybutton.classList.add("hidden");
  }
};

const backToTop = () => {
  window.scrollTo({ top: 0, behavior: "smooth" });
};

mybutton.addEventListener("click", backToTop);
window.addEventListener("scroll", scrollFunction);
Location: src/layouts/Layout.astro:46-70
1

Visibility toggle

The button is hidden by default. When the user scrolls more than 20px, the hidden class is removed.
2

Smooth scroll

Clicking the button calls window.scrollTo() with behavior: "smooth" for animated scrolling.
3

Hover effects

Tailwind classes handle hover, focus, and active states with transitions.

Button states

StateBackgroundShadow
Defaultbg-amber-600shadow-md
Hoverbg-amber-700shadow-lg
Focusbg-amber-700shadow-lg
Activebg-amber-800shadow-lg

Parallax header effect

The main header uses a parallax effect for depth:
src/layouts/Layout.astro
let header = document.querySelector('.main-header');

window.addEventListener('scroll', () => {
  const offset = window.pageYOffset;
  // Move background at half the scroll speed
  header.style.backgroundPositionY = offset * 0.3 + 'px';
});
Location: src/layouts/Layout.astro:36-42
The background moves at 30% of the scroll speed (0.3 multiplier), creating a subtle depth effect.

Creating custom animations

You can combine utility classes to create custom effects:

Example: Slide and fade

<div 
  class="translate-y-10 opacity-0 
         intersect:translate-y-0 intersect:opacity-100 
         transition duration-700 ease-out"
>
  <!-- Content -->
</div>

Example: Scale and rotate

<div 
  class="scale-0 rotate-45 
         intersect:scale-100 intersect:rotate-0 
         transition duration-1000"
>
  <!-- Content -->
</div>

Example: Multiple property animation

<div 
  class="scale-75 opacity-0 blur-sm 
         intersect:scale-100 intersect:opacity-100 intersect:blur-0 
         transition-all duration-500"
>
  <!-- Content -->
</div>
Use transition-all to animate all changing properties simultaneously.

Performance considerations

1

Use transform and opacity

These properties are GPU-accelerated and perform best:
transform: scale(), translateX(), translateY(), rotate()
opacity: 0 to 1
2

Avoid animating layout properties

These properties cause reflows and hurt performance:
width, height, margin, padding, top, left
3

Use animate-once

Prevent repeated animations on scroll:
animate-once
4

Reduce motion for accessibility

Respect user preferences:
motion-reduce:animate-none
Too many simultaneous animations can impact performance. Stagger animations using delays to spread the workload.

Accessibility best practices

Respect reduced motion preference

<div 
  class="intersect:animate-fade-up 
         motion-reduce:animate-none 
         motion-reduce:opacity-100"
>
  <!-- Content -->
</div>
This disables animations for users who have enabled “reduce motion” in their system settings.

Ensure content is accessible without animation

  • Never hide critical content behind animations
  • Use animate-once to prevent distracting loops
  • Maintain sufficient contrast during all animation states

Provide alternative indicators

For interactive elements like ScrollToTop, ensure:
  • Clear focus states (focus:outline, focus:ring)
  • Adequate color contrast (WCAG AA minimum)
  • Touch-friendly sizing (minimum 44x44px)

Product catalog

See staggered scale animations in action

Fragrances

View the fade-up animation example

Contact form

Learn about form interactions and states

Build docs developers (and LLMs) love