lexington®
Use code LEX40 at checkout
7k+ customers.
How to create a product details page with Tailwind CSS and Alpine.js
Published on November 10, 2025 by Michael Andreuzza
Building a Complete Ecommerce Product Page
Product detail pages are the heart of any ecommerce site. Today, we’ll create a comprehensive product page featuring an interactive image gallery, tabbed content sections, color and size selectors, customer reviews, and purchase actions—all built with Alpine.js and Tailwind CSS.
What You’ll Learn
- How to create an interactive image gallery with thumbnails
- Building tabbed content sections (Details, Shipping, Returns, Reviews)
- Implementing color and size selection interfaces
- Adding customer reviews with dynamic data
- Creating responsive product layouts
- Best practices for ecommerce UI components
The Complete Product Page Structure
Here’s the full product page code:
<Wrapper class="py-24">
<div class="grid grid-cols-1 gap-8 lg:grid-cols-3">
<!-- Left: Product copy + Tabs -->
<div class="flex flex-col order-last lg:order-none">
<div class="flex items-center justify-between">
<h2 class="text-lg font-medium md:text-xl">
Nike Air Force 1´07 Fresh
</h2>
<p class="text-lg md:text-xl">$190</p>
</div>
<p class="mt-4 text-base text-zinc-500">
Hitting the field in the late '60s, adidas airmaxS quickly became
soccer's "it" shoe.
</p>
<!-- Tabs -->
<div
x-data="{ tab: 'tab1' }"
class="mt-8 divide-y divide-zinc-200 border-y border-zinc-200"
>
<ul
class="flex gap-4 border-b border-zinc-200 text-sm text-zinc-500"
role="tablist"
>
<li class="-mb-px">
<a
@click.prevent="tab = 'tab1'"
href="#"
role="tab"
aria-selected="true"
class="inline-block py-2 font-medium border-b-2 border-transparent"
:class="{ 'text-zinc-700 border-zinc-600' : tab === 'tab1' }"
>Details</a
>
</li>
<li class="-mb-px">
<a
@click.prevent="tab = 'tab2'"
href="#"
role="tab"
aria-selected="false"
class="inline-block py-2 font-medium border-b-2 border-transparent"
:class="{ 'text-zinc-700 border-zinc-600' : tab === 'tab2' }"
>Shipping</a
>
</li>
<li class="-mb-px">
<a
@click.prevent="tab = 'tab3'"
href="#"
role="tab"
aria-selected="false"
class="inline-block py-2 font-medium border-b-2 border-transparent"
:class="{ 'text-zinc-700 border-zinc-600' : tab === 'tab3' }"
>Returns</a
>
</li>
<li class="-mb-px">
<a
@click.prevent="tab = 'tab4'"
href="#"
role="tab"
aria-selected="false"
class="inline-block py-2 font-medium border-b-2 border-transparent"
:class="{ 'text-zinc-700 border-zinc-600' : tab === 'tab4' }"
>Reviews</a
>
</li>
</ul>
<div class="content py-4 pt-4 text-left text-zinc-500">
<div x-show="tab==='tab1'">
<section class="py-4">
<p class="text-sm">
This product is crafted from high-quality materials designed for
durability and comfort. It features modern design elements and
is perfect for everyday use.
</p>
<ul
role="list"
class="mt-4 list-disc space-y-1 pl-5 text-xs text-zinc-600 marker:text-zinc-500"
>
<li>Versatile lace configurations</li>
<li>Breathable and spacious interior fit</li>
<li>Durable leather overlays</li>
<li>Cushioned insole for added comfort</li>
<li>Reinforced eyelets for secure lacing</li>
<li>Stitching built for durability</li>
<li>Water-resistant materials</li>
</ul>
<p class="mt-8 text-sm">
<strong class="text-black">True to size</strong>. We recommend
ordering your usual size.
</p>
</section>
</div>
<div x-show="tab==='tab2'" style="display: none">
<section class="py-4">
<p class="text-sm">
We offer free standard shipping on all orders above $50. Express
shipping options are available at checkout for an additional
fee.
</p>
</section>
</div>
<div x-show="tab==='tab3'" style="display: none">
<section class="py-4">
<p class="text-sm">
We accept returns within 30 days of purchase. Items must be in
their original condition and packaging. Please visit our returns
page for more details.
</p>
</section>
</div>
<div x-show="tab==='tab4'" style="display: none">
<section class="py-4">
<div
class="flex flex-col"
x-data="{ testimonials: [
{ name: 'Fernando Monte', date: '2025-12-01', datetime: '2025-12-01', img: '/avatars/1.png', text: 'The Nike Air Max 90 Black exceeded my expectations! Super comfortable and perfect for both running and casual wear.' },
{ name: 'Juan Echanove', date: '2025-11-15', datetime: '2025-11-15', img: '/avatars/2.png', text: 'Stylish and lightweight. I wear them everywhere, but the fit was slightly snug at first. Broke in nicely after a few days.' }
] }"
>
<template x-for="t in testimonials" :key="t.datetime">
<div class="flex gap-x-4 py-4">
<div class="flex-none">
<img
:src="t.img"
:alt="t.name"
class="size-10 rounded-full bg-zinc-100"
/>
</div>
<div>
<h3
class="text-base font-medium text-zinc-900"
x-text="t.name"
></h3>
<p class="text-xs text-zinc-500">
<time :datetime="t.datetime" x-text="t.date"></time>
</p>
<p class="mt-1 text-sm text-zinc-500" x-text="t.text"></p>
</div>
</div>
</template>
</div>
<div class="mt-8 flex">
<button
class="relative flex h-9 w-full select-none items-center justify-center rounded-md bg-zinc-50 px-4 text-center text-sm font-medium text-zinc-600 outline outline-zinc-100 transition-colors duration-200 ease-in-out hover:bg-zinc-200 focus-visible:z-10 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-zinc-600"
>
Write a review
</button>
</div>
</section>
</div>
</div>
</div>
</div>
<!-- Middle: Product Image Gallery -->
<div
x-data="{
activeImage: 0,
images: [
'https://oxbowui.com/store/shoes/airmax/1.png',
'https://oxbowui.com/store/shoes/airmax/2.png',
'https://oxbowui.com/store/shoes/airmax/3.png',
'https://oxbowui.com/store/shoes/airmax/4.png',
'https://oxbowui.com/store/shoes/airmax/5.png',
'https://oxbowui.com/store/shoes/airmax/6.png'
]
}"
class="order-first flex flex-col gap-2 lg:order-none"
>
<!-- Main Image -->
<div class="aspect-square overflow-hidden rounded-2xl bg-zinc-200">
<img
:src="images[activeImage]"
class="size-full aspect-square object-cover"
alt="Product image"
/>
</div>
<!-- Thumbnails -->
<div class="grid w-full grid-cols-6 gap-2">
<template x-for="(image, index) in images" :key="index">
<button
@click="activeImage = index"
:class="{ 'ring-2 ring-zinc-900' : activeImage === index }"
class="size-full aspect-square overflow-hidden rounded-xl bg-zinc-200"
aria-label="Select image thumbnail"
>
<img
:src="image"
class="size-full aspect-square object-cover"
alt="Product thumbnail"
/>
</button>
</template>
</div>
</div>
<!-- Right: Product Options + Actions -->
<div>
<div class="mt-4 flex flex-col gap-4">
<!-- Color Selector -->
<div x-data="{ activeColor: null }">
<p class="text-xs uppercase text-zinc-500">Color</p>
<fieldset
aria-label="Choose a color"
class="mt-2"
x-data="{
colors: [
{ name: ' Black', ring: 'ring-zinc-700', bg: 'bg-zinc-400' },
{ name: ' Gray', ring: 'ring-zinc-300', bg: 'bg-zinc-200' },
{ name: ' Purple', ring: 'ring-purple-300', bg: 'bg-purple-200' },
{ name: ' Pink', ring: 'ring-pink-300', bg: 'bg-pink-200' },
{ name: ' Red', ring: 'ring-red-300', bg: 'bg-red-200' },
{ name: ' Orange', ring: 'ring-orange-300', bg: 'bg-orange-200' },
{ name: ' Yellow', ring: 'ring-yellow-300', bg: 'bg-yellow-200' },
{ name: ' Blue', ring: 'ring-blue-300', bg: 'bg-blue-200' },
{ name: ' Cyan', ring: 'ring-cyan-300', bg: 'ring-cyan-200' },
{ name: ' Green', ring: 'ring-green-300', bg: 'bg-green-200' },
{ name: ' Teal', ring: 'ring-teal-300', bg: 'bg-teal-200' },
],
activeColor: null
}"
>
<div class="flex flex-wrap items-center gap-3">
<template x-for="color in colors" :key="color.name">
<label
:aria-label="color.name"
class="relative -m-0.5 flex cursor-pointer items-center justify-center rounded-full p-0.5 duration-300 focus:outline-none"
:class="{ 'ring-2 ring-offset-2 ring-zinc-900': activeColor === color.name }"
>
<input
type="radio"
name="color-choice"
:value="color.name"
class="sr-only"
@click="activeColor = color.name"
/>
<span
aria-hidden="true"
class="size-6 rounded-full ring-1"
:class="`${color.ring} ${color.bg}`"
></span>
</label>
</template>
</div>
</fieldset>
</div>
<!-- Size Selector -->
<div
x-data="{ sizeSystem: 'US', activeSize: null, sizes: { US: ['6','7','8','9','10','11','12','13'], EU: ['39','40','41','42','43','44','45','46'] } }"
>
<div class="flex items-center justify-between">
<p class="text-xs uppercase text-zinc-500">Shoe size</p>
<div>
<label for="size-system" class="sr-only">Size System</label>
<select
id="size-system"
x-model="sizeSystem"
@change="activeSize = null"
class="block h-8 w-auto appearance-none rounded-lg border border-transparent bg-white pl-2.5 pr-8 py-2 text-xs text-zinc-900 ring-1 ring-zinc-200 placeholder-zinc-400 duration-300 focus:bg-transparent focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2"
>
<option value="US">US</option>
<option value="EU">EU</option>
</select>
</div>
</div>
<div class="mt-2 grid grid-cols-4 gap-2 divide-x divide-zinc-200">
<template x-for="size in sizes[sizeSystem]" :key="size">
<div>
<input
type="radio"
:id="'size-' + size"
:value="size"
name="size-choice"
x-model="activeSize"
class="peer sr-only"
/>
<label
:for="'size-' + size"
x-text="size"
class="flex cursor-pointer items-center justify-center rounded-md bg-white px-3 py-2 text-sm font-medium text-zinc-500 ring-1 ring-zinc-200 duration-300 peer-checked:text-zinc-500 peer-checked:ring-2 peer-checked:ring-zinc-900 peer-checked:ring-offset-2"
></label>
</div>
</template>
</div>
</div>
</div>
<!-- Action Buttons -->
<div class="mt-8 flex flex-col gap-2">
<button
class="relative flex h-9 w-full select-none items-center justify-center rounded-md bg-zinc-900 px-4 text-center text-sm font-medium text-white outline outline-zinc-900 transition-colors duration-200 ease-in-out hover:bg-zinc-950 focus-visible:z-10 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-zinc-950"
>
Add to Cart
</button>
<button
class="relative flex h-9 w-full select-none items-center justify-center rounded-md bg-zinc-50 px-4 text-center text-sm font-medium text-zinc-600 outline outline-zinc-100 transition-colors duration-200 ease-in-out hover:bg-zinc-200 focus-visible:z-10 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-zinc-600"
>
Buy Now
</button>
</div>
<p class="mt-1 text-sm text-zinc-500">
Free shipping over $50 if you behave
</p>
</div>
</div>
</Wrapper>
Breaking Down the Code
1. The Layout Grid
<div class="grid grid-cols-1 gap-8 lg:grid-cols-3"></div>
- Responsive grid: Single column on mobile, three columns on large screens
- Order classes:
order-firstandorder-lastcontrol column positioning on mobile - Gap spacing: Consistent 2rem gap between sections
2. Product Information Section
<div class="flex items-center justify-between">
<h2 class="text-lg font-medium md:text-xl">Nike Air Force 1´07 Fresh</h2>
<p class="text-lg md:text-xl">$190</p>
</div>
- Flexible header: Product name on left, price on right
- Responsive typography: Larger text on medium+ screens
3. Tabbed Content System
<div
x-data="{ tab: 'tab1' }"
class="mt-8 divide-y divide-zinc-200 border-y border-zinc-200"
></div>
- Alpine state:
tabvariable tracks active tab - Visual separators: Borders and dividers create clean sections
- Tab navigation: Click handlers update the active tab state
4. Tab Content Areas
<div x-show="tab==='tab1'">
<section class="py-4">
<!-- Product details content -->
</section>
</div>
- Conditional display:
x-showshows content based on active tab - Structured content: Each tab has semantic HTML sections
- Product information: Details, shipping, returns, and reviews
5. Image Gallery Component
<div
x-data="{ activeImage: 0, images: [...] }"
class="order-first flex flex-col gap-2 lg:order-none"
></div>
- Gallery state: Tracks active image and image array
- Main image display: Large aspect-square container
- Thumbnail grid: 6-column grid of clickable thumbnails
- Active state: Ring styling highlights selected thumbnail
6. Color Selection Interface
<fieldset
aria-label="Choose a color"
x-data="{ colors: [...], activeColor: null }"
></fieldset>
- Accessible form: Proper fieldset and labels
- Color array: Objects with names, ring colors, and backgrounds
- Radio inputs: Screen reader accessible selection
- Visual feedback: Ring styling for selected colors
7. Size Selection System
<div
x-data="{ sizeSystem: 'US', activeSize: null, sizes: { US: [...], EU: [...] } }"
></div>
- Dual sizing: US and EU size systems
- Dynamic switching: Size system dropdown changes available options
- Grid layout: 4-column responsive grid for size buttons
- Peer styling: CSS peer classes for checked states
8. Action Buttons
<button
class="relative flex h-9 w-full select-none items-center justify-center rounded-md bg-zinc-900 px-4 text-center text-sm font-medium text-white outline outline-zinc-900 transition-colors duration-200 ease-in-out hover:bg-zinc-950"
>
Add to Cart
</button>
- Primary CTA: Dark background, prominent styling
- Secondary action: Light background, subtle styling
- Accessibility: Focus states and proper contrast
- Responsive: Full width on mobile, auto width on larger screens
Alpine.js State Management
The product page uses multiple Alpine.js data objects:
Tab Management
x-data="{ tab: 'tab1' }"
- Simple string state for active tab
- Click handlers update the tab value
Image Gallery
x-data="{
activeImage: 0,
images: ['url1', 'url2', 'url3', ...]
}"
activeImage: Index of currently displayed imageimages: Array of image URLs- Click handlers update
activeImage
Color Selection
x-data="{ activeColor: null }"
- Tracks selected color name
- Radio button clicks update the value
Size Selection
x-data="{
sizeSystem: 'US',
activeSize: null,
sizes: { US: [...], EU: [...] }
}"
sizeSystem: Current sizing system (US/EU)activeSize: Selected size valuesizes: Object with size arrays for each system
Key Features Explained
Responsive Design: The layout adapts from single column on mobile to three columns on desktop, with proper content ordering.
Interactive Gallery: Click thumbnails to change the main product image, with visual feedback for the active selection.
Tabbed Information: Clean organization of product details, shipping, returns, and customer reviews.
Variant Selection: Color and size pickers with proper accessibility and visual feedback.
Customer Reviews: Dynamic testimonial display with user avatars and timestamps.
Call-to-Action: Prominent purchase buttons with hover states and accessibility features.
Customization Ideas
- Add quantity selectors for multiple item purchases
- Include product variant pricing (different prices for colors/sizes)
- Add wishlist/favorite functionality
- Implement zoom functionality for product images
- Include product availability indicators
- Add social sharing buttons
- Integrate with shopping cart state management
This comprehensive product page provides an excellent user experience with smooth interactions, clear information hierarchy, and professional ecommerce functionality. The combination of Alpine.js reactivity and Tailwind CSS styling creates a modern, performant shopping experience.
Complete Code
<Wrapper class="py-24">
<div class="grid grid-cols-1 gap-8 lg:grid-cols-3">
<!-- Left: Product copy + Tabs -->
<div class="flex flex-col order-last lg:order-none">
<div class="flex items-center justify-between">
<h2 class="text-lg font-medium md:text-xl">
Nike Air Force 1´07 Fresh
</h2>
<p class="text-lg md:text-xl">$190</p>
</div>
<p class="mt-4 text-base text-zinc-500">
Hitting the field in the late '60s, adidas airmaxS quickly became
soccer's "it" shoe.
</p>
<!-- Tabs -->
<div
x-data="{ tab: 'tab1' }"
class="mt-8 divide-y divide-zinc-200 border-y border-zinc-200"
>
<ul
class="flex gap-4 border-b border-zinc-200 text-sm text-zinc-500"
role="tablist"
>
<li class="-mb-px">
<a
@click.prevent="tab = 'tab1'"
href="#"
role="tab"
aria-selected="true"
class="inline-block py-2 font-medium border-b-2 border-transparent"
:class="{ 'text-zinc-700 border-zinc-600' : tab === 'tab1' }"
>Details</a
>
</li>
<li class="-mb-px">
<a
@click.prevent="tab = 'tab2'"
href="#"
role="tab"
aria-selected="false"
class="inline-block py-2 font-medium border-b-2 border-transparent"
:class="{ 'text-zinc-700 border-zinc-600' : tab === 'tab2' }"
>Shipping</a
>
</li>
<li class="-mb-px">
<a
@click.prevent="tab = 'tab3'"
href="#"
role="tab"
aria-selected="false"
class="inline-block py-2 font-medium border-b-2 border-transparent"
:class="{ 'text-zinc-700 border-zinc-600' : tab === 'tab3' }"
>Returns</a
>
</li>
<li class="-mb-px">
<a
@click.prevent="tab = 'tab4'"
href="#"
role="tab"
aria-selected="false"
class="inline-block py-2 font-medium border-b-2 border-transparent"
:class="{ 'text-zinc-700 border-zinc-600' : tab === 'tab4' }"
>Reviews</a
>
</li>
</ul>
<div class="content py-4 pt-4 text-left text-zinc-500">
<div x-show="tab==='tab1'">
<section class="py-4">
<p class="text-sm">
This product is crafted from high-quality materials designed for
durability and comfort. It features modern design elements and
is perfect for everyday use.
</p>
<ul
role="list"
class="mt-4 list-disc space-y-1 pl-5 text-xs text-zinc-600 marker:text-zinc-500"
>
<li>Versatile lace configurations</li>
<li>Breathable and spacious interior fit</li>
<li>Durable leather overlays</li>
<li>Cushioned insole for added comfort</li>
<li>Reinforced eyelets for secure lacing</li>
<li>Stitching built for durability</li>
<li>Water-resistant materials</li>
</ul>
<p class="mt-8 text-sm">
<strong class="text-black">True to size</strong>. We recommend
ordering your usual size.
</p>
</section>
</div>
<div x-show="tab==='tab2'" style="display: none">
<section class="py-4">
<p class="text-sm">
We offer free standard shipping on all orders above $50. Express
shipping options are available at checkout for an additional
fee.
</p>
</section>
</div>
<div x-show="tab==='tab3'" style="display: none">
<section class="py-4">
<p class="text-sm">
We accept returns within 30 days of purchase. Items must be in
their original condition and packaging. Please visit our returns
page for more details.
</p>
</section>
</div>
<div x-show="tab==='tab4'" style="display: none">
<section class="py-4">
<div
class="flex flex-col"
x-data="{ testimonials: [
{ name: 'Fernando Monte', date: '2025-12-01', datetime: '2025-12-01', img: '/avatars/1.png', text: 'The Nike Air Max 90 Black exceeded my expectations! Super comfortable and perfect for both running and casual wear.' },
{ name: 'Juan Echanove', date: '2025-11-15', datetime: '2025-11-15', img: '/avatars/2.png', text: 'Stylish and lightweight. I wear them everywhere, but the fit was slightly snug at first. Broke in nicely after a few days.' }
] }"
>
<template x-for="t in testimonials" :key="t.datetime">
<div class="flex gap-x-4 py-4">
<div class="flex-none">
<img
:src="t.img"
:alt="t.name"
class="size-10 rounded-full bg-zinc-100"
/>
</div>
<div>
<h3
class="text-base font-medium text-zinc-900"
x-text="t.name"
></h3>
<p class="text-xs text-zinc-500">
<time :datetime="t.datetime" x-text="t.date"></time>
</p>
<p class="mt-1 text-sm text-zinc-500" x-text="t.text"></p>
</div>
</div>
</template>
</div>
<div class="mt-8 flex">
<button
class="relative flex h-9 w-full select-none items-center justify-center rounded-md bg-zinc-50 px-4 text-center text-sm font-medium text-zinc-600 outline outline-zinc-100 transition-colors duration-200 ease-in-out hover:bg-zinc-200 focus-visible:z-10 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-zinc-600"
>
Write a review
</button>
</div>
</section>
</div>
</div>
</div>
</div>
<!-- Middle: Product Image Gallery -->
<div
x-data="{
activeImage: 0,
images: [
'https://oxbowui.com/store/shoes/airmax/1.png',
'https://oxbowui.com/store/shoes/airmax/2.png',
'https://oxbowui.com/store/shoes/airmax/3.png',
'https://oxbowui.com/store/shoes/airmax/4.png',
'https://oxbowui.com/store/shoes/airmax/5.png',
'https://oxbowui.com/store/shoes/airmax/6.png'
]
}"
class="order-first flex flex-col gap-2 lg:order-none"
>
<!-- Main Image -->
<div class="aspect-square overflow-hidden rounded-2xl bg-zinc-200">
<img
:src="images[activeImage]"
class="size-full aspect-square object-cover"
alt="Product image"
/>
</div>
<!-- Thumbnails -->
<div class="grid w-full grid-cols-6 gap-2">
<template x-for="(image, index) in images" :key="index">
<button
@click="activeImage = index"
:class="{ 'ring-2 ring-zinc-900' : activeImage === index }"
class="size-full aspect-square overflow-hidden rounded-xl bg-zinc-200"
aria-label="Select image thumbnail"
>
<img
:src="image"
class="size-full aspect-square object-cover"
alt="Product thumbnail"
/>
</button>
</template>
</div>
</div>
<!-- Right: Product Options + Actions -->
<div>
<div class="mt-4 flex flex-col gap-4">
<!-- Color Selector -->
<div x-data="{ activeColor: null }">
<p class="text-xs uppercase text-zinc-500">Color</p>
<fieldset
aria-label="Choose a color"
class="mt-2"
x-data="{
colors: [
{ name: ' Black', ring: 'ring-zinc-700', bg: 'bg-zinc-400' },
{ name: ' Gray', ring: 'ring-zinc-300', bg: 'bg-zinc-200' },
{ name: ' Purple', ring: 'ring-purple-300', bg: 'bg-purple-200' },
{ name: ' Pink', ring: 'ring-pink-300', bg: 'bg-pink-200' },
{ name: ' Red', ring: 'ring-red-300', bg: 'bg-red-200' },
{ name: ' Orange', ring: 'ring-orange-300', bg: 'bg-orange-200' },
{ name: ' Yellow', ring: 'ring-yellow-300', bg: 'bg-yellow-200' },
{ name: ' Blue', ring: 'ring-blue-300', bg: 'bg-blue-200' },
{ name: ' Cyan', ring: 'ring-cyan-300', bg: 'ring-cyan-200' },
{ name: ' Green', ring: 'ring-green-300', bg: 'bg-green-200' },
{ name: ' Teal', ring: 'ring-teal-300', bg: 'bg-teal-200' },
],
activeColor: null
}"
>
<div class="flex flex-wrap items-center gap-3">
<template x-for="color in colors" :key="color.name">
<label
:aria-label="color.name"
class="relative -m-0.5 flex cursor-pointer items-center justify-center rounded-full p-0.5 duration-300 focus:outline-none"
:class="{ 'ring-2 ring-offset-2 ring-zinc-900': activeColor === color.name }"
>
<input
type="radio"
name="color-choice"
:value="color.name"
class="sr-only"
@click="activeColor = color.name"
/>
<span
aria-hidden="true"
class="size-6 rounded-full ring-1"
:class="`${color.ring} ${color.bg}`"
></span>
</label>
</template>
</div>
</fieldset>
</div>
<!-- Size Selector -->
<div
x-data="{ sizeSystem: 'US', activeSize: null, sizes: { US: ['6','7','8','9','10','11','12','13'], EU: ['39','40','41','42','43','44','45','46'] } }"
>
<div class="flex items-center justify-between">
<p class="text-xs uppercase text-zinc-500">Shoe size</p>
<div>
<label for="size-system" class="sr-only">Size System</label>
<select
id="size-system"
x-model="sizeSystem"
@change="activeSize = null"
class="block h-8 w-auto appearance-none rounded-lg border border-transparent bg-white pl-2.5 pr-8 py-2 text-xs text-zinc-900 ring-1 ring-zinc-200 placeholder-zinc-400 duration-300 focus:bg-transparent focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2"
>
<option value="US">US</option>
<option value="EU">EU</option>
</select>
</div>
</div>
<div class="mt-2 grid grid-cols-4 gap-2 divide-x divide-zinc-200">
<template x-for="size in sizes[sizeSystem]" :key="size">
<div>
<input
type="radio"
:id="'size-' + size"
:value="size"
name="size-choice"
x-model="activeSize"
class="peer sr-only"
/>
<label
:for="'size-' + size"
x-text="size"
class="flex cursor-pointer items-center justify-center rounded-md bg-white px-3 py-2 text-sm font-medium text-zinc-500 ring-1 ring-zinc-200 duration-300 peer-checked:text-zinc-500 peer-checked:ring-2 peer-checked:ring-zinc-900 peer-checked:ring-offset-2"
></label>
</div>
</template>
</div>
</div>
</div>
<!-- Action Buttons -->
<div class="mt-8 flex flex-col gap-2">
<button
class="relative flex h-9 w-full select-none items-center justify-center rounded-md bg-zinc-900 px-4 text-center text-sm font-medium text-white outline outline-zinc-900 transition-colors duration-200 ease-in-out hover:bg-zinc-950 focus-visible:z-10 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-zinc-950"
>
Add to Cart
</button>
<button
class="relative flex h-9 w-full select-none items-center justify-center rounded-md bg-zinc-50 px-4 text-center text-sm font-medium text-zinc-600 outline outline-zinc-100 transition-colors duration-200 ease-in-out hover:bg-zinc-200 focus-visible:z-10 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-zinc-600"
>
Buy Now
</button>
</div>
<p class="mt-1 text-sm text-zinc-500">
Free shipping over $50 if you behave
</p>
</div>
</div>
</Wrapper> /Michael Andreuzza