Building Stripe.com's Tabbed Preview Widget From Scratch in 30 Minutes
In this video I try to build a fully functional tabbed preview widget in HTML and CSS from scratch in 30 minutes—without looking at the original code. After the time’s up, I peek under the hood to see the approach of the original author and compare and contrast it to my approach.
The build process
Setting up a development environment
For this short project I used a very simple setup: an index.html
file served by browser-sync
for automatic reloads on save. It can be run without previous download or install via npx
, which is included by default in node/npm installations.
Configuring the widget’s container
Since this widget doesn’t appear to respond to the window size, I used the macOS screenshot tool (command
-shift
-4
) to measure the dimensions and simply hard-code them into the CSS.
Managing the active tab state
The original Stripe implementation uses JavaScript to maintain the state of the currently active tab, which is a perfectly reasonable approach. I thought it might be fun to see if we could do it without JavaScript. I landed on using radio-type HTML inputs and some CSS selector tricks to achieve the same effect. In a professional setting, I would have likely used JavaScript—one could argue that this is an inappropriate use of radio inputs since this is an informational widget and not part of a form with user-provided data.
The book I referenced in the video is called Resilient Web Design by Jeremy Keith and is freely available to read online.
Sliding the content left and right based on the active tab
I wrapped the code snippets in a container and positioned it absolutely, altering the left
property based on the active tab. We discuss a better approach to this later on when we inspect Stripe’s solution.
Giving the widget a 3D appearance
To give the widget a 3D appearance, I rotated the widget around the X and Y axes, but it didn’t quite have the right effect. I should have used the rotate3d()
function instead.
Adding a shine effect
For the shine effect, I added an ::after
pseudo-element, positioned absolutely to stretch the width and height of the container, and added a background gradient. To keep the text beneath it selectable (and tabs clickable), pointer-events: none
was required so that mouse events would fall through it.
My final code
Here is what I ended up with at the end of the session.
Inspecting the original Stripe code
Differences with my approach
Aside from the fact that the Stripe code was much more polished (with additional borders, typography, syntax highlighting, etc.), there were a number things about the original code that were much improved to my version. Here are a couple:
- Rather than transitioning the
left
property to slide the code back and forth, the original author usedtranslateX()
, which is more performant. - The original author used the
<figure>
element to wrap this widget, which is much more semantically correct than thediv
I used.
Nice touches
Stripe is known for adding a level of polish and detail that most engineering teams can only dream of. Here are just a couple that I noticed:
- Using
::before
and::after
pseudo-elements with transparent-to-opaque background gradients, the text had an appearance of sliding “under” the edges of the widget as it moved back and forth. - A tasteful
box-shadow
was added to augment the 3D effect of the widget.
Conclusion
Any questions or suggestions about my tooling, approach, development style? What would you have done differently? I’d love to hear from you. Find me on Twitter or leave a comment on the video.
Tools
Here are the tools I used in this video:
- Text editor: Visual Studio Code with theme Right in the Teals by themer, and Fira Code font
- Browser: Brave
- Development server: Browsersync launched under npx
- Application launcher: Alfred
- Color picker: Sip