How to make a small pulsating animation
How to create a new animation for Tailwind and use it in your Rails app

The other day, I added a deals page to the GoodEnoughTesting website and considered making the menu item a bit more visible. I wanted it to pulsate just a bit, without being too much.
Tailwind already has an animation called pulse, but I wanted something else. I wanted something that would scale a bit and then come back.
This is what I implemented:

First, I added the following in the application.tailwind.css where I define a 3 keyframes, and I scale it just a bit over at 50% (or the middle frame):
@keyframes pulsate {
0% {
transform: scale(1);
}
50% {
transform: scale(1.10);
}
100% {
transform: scale(1);
}
}
Then I extended Tailwind to include this as animation, by adding the following in the config/tailwind.config.js:
module.exports = {
theme: {
extend: {
+ animation: {
+ 'pulsate': 'pulsate 2s ease-in-out infinite',
+ }
},
},
}
And now I can use it in the header menu:
<a href="" class="motion-safe:animate-pulsate">
A pulsating link
</a>
I used motion-safe modifier to respected the choice of the user if they requested reduced motion.
Extracting a component
I am using Phlex when building the UI for the GoodEnoughTesting so I want to extract the menu item to a series of components that I can re-use when needed.
First I created a Menu::ItemComponent:
module Menu
class ItemComponent < ApplicationComponent
include Phlex::Rails::Helpers::LinkTo
CLASS = <<~DEFAULT
inline-block
no-underline
font-bold text-base uppercase leading-6 antialiased
x-2.5 py-2 px-2
align-baseline
hover:bg-blue-200 hover:rounded-lg
DEFAULT
def initialize(url:, text: nil, pulsate: false, text_color: "", **options)
@text = text
@url = url
@options = options
@pulsate = pulsate
@text_color = text_color
end
def template(&)
if @text.present?
link_to(@text, @url, class: css_rules)
else
link_to(@url, class: css_rules, &)
end
end
private
def css_rules = "#{CLASS} #{pulsate_css} #{@text_color}"
def pulsate_css = (@pulsate ? "motion-safe:animate-pulsate" : "")
end
end
And then I created simple versions of this component.
Menu::PrimaryComponent is the normal menu item, with a dark gray color:
module Menu
class PrimaryComponent < ItemComponent
def initialize(url:, text: nil, **options)
pulsate = false
text_color = "text-gray-800"
super(url:, text:, pulsate:, text_color:, **options)
end
end
end
Menu::BlueComponent is a menu item with the blue text:
module Menu
class BlueComponent < ItemComponent
def initialize(url:, text: nil, **options)
pulsate = false
text_color = "text-blue-700"
super(url:, text:, pulsate:, text_color:, **options)
end
end
end
Menu::PulsatingComponent is the one that is setting the pulsate attribute:
module Menu
class PulsatingComponent < ItemComponent
def initialize(url:, text: nil, **options)
pulsate = true
text_color = "text-blue-700"
super(url:, text:, pulsate:, text_color:, **options)
end
end
end
And then at the end I combine them inside an ERB file like this:
<%= render Menu::PrimaryComponent.new(url: page_path("articles"), text: "Articles and News") %>
<%= render Menu::PulsatingComponent.new(url: page_path("deals"), text: "Deals", pulsate: true) %>
<%= render Menu::PrimaryComponent.new(url: page_path("about"), text: "About") %>
<%= render Menu::PrimaryComponent.new(url: page_path("contact"), text: "Contact") %>
<%= render Menu::BlueComponent.new(url: participants_sign_in_path, text: "Login") %>
If you like this article:
👐 Join my live workshop about goodenoughtesting.com - to learn test design techniques for writing effective tests
👉 Join my Short Ruby Newsletter for weekly Ruby updates from the community and visit rubyandrails.info, a directory with learning content about Ruby.
🤝 Let's connect on Bluesky, Ruby.social, Linkedin, Twitter where I post mostly about Ruby and Ruby on Rails.
🎥 Follow me on my YouTube channel for short videos about Ruby/Rails




