<div class="flex flex-col space-y-4 ">
<a href="#" class="block relative aspect-square overflow-hidden bg-gray-100 rounded-lg group hover:after:!h-0 noAbsolute">
<div class="relative w-full h-full">
<img src="/img/hero/videoLeft.jpg" alt="Product Name" class="w-full h-full object-cover transition-all duration-300 absolute inset-0 group-hover:opacity-0 ">
<img src="/img/hero/videoRight.jpg" alt="Product Name - Vue alternative" class="w-full h-full object-cover transition-all duration-300 absolute inset-0 opacity-0 group-hover:opacity-100">
</div>
<div class="absolute bottom-4 right-1/2 flex space-x-4 translate-x-2/4 duration-300 z-20">
<button type="button" class="group-hover:!btn-dark-ghost btn btn-light-ghost btn-size-sm btn-only-icon">
<svg class=" shrink-0" width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.4907 20.5806C12.4538 20.603 12.4158 20.6259 12.3786 20.6477C12.1434 20.785 11.8519 20.7838 11.6176 20.645L11.5062 20.5774C11.4366 20.5345 11.3359 20.4717 11.2087 20.3901C10.9544 20.227 10.5934 19.9884 10.1613 19.6831C9.2984 19.0734 8.14487 18.193 6.98812 17.1121C4.71943 14.9921 2.25 11.9278 2.25 8.51266C2.25 5.58716 4.58441 3.25 7.5 3.25C9.03842 3.25 10.3421 4.16053 11.1962 4.9489C11.5116 5.24006 11.7824 5.53002 12 5.78205C12.2176 5.53002 12.4884 5.24006 12.8038 4.9489C13.6579 4.16053 14.9616 3.25 16.5 3.25C19.4142 3.25 21.75 5.58579 21.75 8.5C21.75 11.9589 19.283 15.0258 17.0112 17.1406C15.8539 18.218 14.6998 19.0918 13.8365 19.6958C13.4042 19.9983 13.043 20.2343 12.7885 20.3956C12.6612 20.4762 12.5604 20.5382 12.4907 20.5806ZM3.75 8.51266C3.75 11.2516 5.78075 13.9309 8.01225 16.0161C9.10559 17.0378 10.2022 17.8753 11.0268 18.458C11.4296 18.7425 11.7658 18.9651 12.0029 19.1175C12.2398 18.9672 12.5751 18.7476 12.9766 18.4667C13.8007 17.8902 14.8966 17.0598 15.9892 16.0427C18.2172 13.9686 20.25 11.2855 20.25 8.5C20.25 6.41421 18.5858 4.75 16.5 4.75C15.5384 4.75 14.5921 5.33947 13.8212 6.0511C13.4488 6.3949 13.1457 6.74102 12.9356 7.00175C12.8311 7.13154 12.7508 7.23868 12.6977 7.31179C12.6712 7.34831 12.6515 7.37623 12.6391 7.39411L12.626 7.41317L12.624 7.41603C12.4849 7.62448 12.2506 7.75 12 7.75C11.7496 7.75 11.5153 7.62451 11.3762 7.41634L11.374 7.41317L11.3609 7.39411C11.3485 7.37623 11.3288 7.34831 11.3023 7.31179C11.2492 7.23868 11.1689 7.13154 11.0644 7.00175C10.8543 6.74102 10.5512 6.3949 10.1788 6.0511C9.72644 5.63355 9.21372 5.25805 8.67296 5.01901C8.29212 4.85066 7.89737 4.75 7.5 4.75C5.41559 4.75 3.75 6.41284 3.75 8.51266Z" fill="currentColor" />
</svg>
</button>
</div>
</a>
<div class="px-2">
<p class="text-xxs font-semibold text-neutral-500">Brand Name</p>
<h3 class="font-semibold text-neutral-800 pt-0.5">Product Name</h3>
<p class="text-xs text-neutral-500 pt-1">Product description goes here with some details about the item.</p>
<div class="pt-3">
<p class="text-[9px] text-neutral-500">Prix web</p>
<div class="flex items-center gap-2">
<p class="font-semibold text-neutral-800">
99.99 €
</p>
</div>
</div>
</div>
</div>
{# components/slide/slideCard.twig #}
<div
class="flex flex-col space-y-4 {{ class }}"
>
<a
href="{{ slide.url }}"
class="block relative aspect-square overflow-hidden bg-gray-100 rounded-lg group hover:after:!h-0 noAbsolute"
{% if slide.openInNewTab %}target="_blank"{% endif %}
>
<div class="relative w-full h-full">
<img
src="{{ slide.image }}"
alt="{{ slide.name }}"
class="w-full h-full object-cover transition-all duration-300 absolute inset-0 {% if slide.hoverImage is defined and slide.hoverImage %} group-hover:opacity-0 {% endif %}"
>
{% if slide.hoverImage is defined and slide.hoverImage %}
<img
src="{{ slide.hoverImage }}"
alt="{{ slide.name }} - Vue alternative"
class="w-full h-full object-cover transition-all duration-300 absolute inset-0 opacity-0 group-hover:opacity-100"
>
{% endif %}
</div>
<div class="absolute bottom-4 right-1/2 flex space-x-4 translate-x-2/4 duration-300 z-20">
{% if slide.showActionButtons is not defined or slide.showActionButtons %}
{% if slide.showFavoriteButton is not defined or slide.showFavoriteButton %}
{% render "@template-button" with {
color: slide.buttonColor ~ "-ghost",
size: "sm",
type: "only-icon",
icon: {
name: "library--like-outline"
},
button_class: "group-hover:!btn-" ~ slide.buttonColorHover ~ "-ghost"
} %}
{% endif %}
{% if slide.showQuickViewButton is not defined or slide.showQuickViewButton %}
{% render "@template-button" with {
color: "light-ghost",
size: "sm",
type: "only-icon",
icon: {
name: "library--camera"
},
button_class: "group-hover:!btn-dark-ghost"
} %}
{% endif %}
{% endif %}
</div>
</a>
<div class="px-2">
{% if slide.brand is defined and slide.brand %}
<p class="text-xxs font-semibold text-neutral-500" {{ slide.alpineAttribute.slideInfo.brand }}>{{ slide.brand }}</p>
{% endif %}
{% if slide.inlinePrice is defined and slide.inlinePrice %}
<div class="flex justify-between items-end py-2">
{% if slide.name is defined and slide.name %}
<h3 class="font-semibold text-neutral-800 pt-0.5" {{ slide.alpineAttribute.slideInfo.name }}>{{ slide.name }}</h3>
{% endif %}
<div class="flex flex-col text-right">
{% if slide.webLabel is defined and slide.webLabel %}
<span class="text-[9px] text-neutral-500 leading-[0] uppercase" {{ slide.alpineAttribute.slideInfo.webLabel }}>{{ slide.webLabel }}</span>
{% endif %}
{% if slide.price is defined and slide.price %}
<span class="font-semibold text-neutral-800" {{ slide.alpineAttribute.slideInfo.price }}>
{{ slide.price }} €
</span>
{% endif %}
</div>
</div>
{% else %}
{% if slide.name is defined and slide.name %}
<h3 class="font-semibold text-neutral-800 pt-0.5" {{ slide.alpineAttribute.slideInfo.name }}>{{ slide.name }}</h3>
{% endif %}
{% endif %}
{% if slide.description is defined and slide.description %}
<p class="text-xs text-neutral-500 pt-1" {{ slide.alpineAttribute.slideInfo.description }}>{{ slide.description }}</p>
{% endif %}
{% if slide.colors is defined and slide.colors %}
<p class="text-xs text-neutral-500 pt-2" {{ slide.alpineAttribute.slideInfo.colors }}>{{ slide.colors }}</p>
{% endif %}
{% if not (slide.inlinePrice is defined and slide.inlinePrice) %}
<div class="pt-3">
<p class="text-[9px] text-neutral-500" {{ slide.alpineAttribute.slideInfo.webLabel }}>{{ slide.webLabel }}</p>
<div class="flex items-center gap-2">
<p class="font-semibold text-neutral-800" {{ slide.alpineAttribute.slideInfo.price }}>
{{ slide.price }} €
</p>
{% if slide.oldPrice and slide.discountText %}
<p class="text-neutral-500 text-xs line-through">
{{ slide.oldPrice }} €
</p>
<p class="text-green-700 text-xs">
{{ slide.discountText }}
</p>
{% endif %}
</div>
</div>
{% endif %}
{% if slide.addCart is defined and slide.addCart %}
<div class="flex justify-center">
{% render "@template-button" with {
label: "Ajouter au panier",
color: "dark-ghost",
size:"sm"
} %}
</div>
{% endif %}
</div>
</div>
{
"slide": {
"id": "123",
"url": "#",
"image": "/img/hero/videoLeft.jpg",
"hoverImage": "/img/hero/videoRight.jpg",
"brand": "Brand Name",
"name": "Product Name",
"description": "Product description goes here with some details about the item.",
"color": "3 couleurs disponibles",
"webLabel": "Prix web",
"price": "99.99",
"openInNewTab": false,
"showActionButtons": true,
"showFavoriteButton": true,
"showQuickViewButton": false,
"class": "",
"buttonColor": "light",
"buttonColorHover": "dark"
}
}
The Product Card component is used to display product information in a standardized layout. It is particularly suited for product grids, carousels, and recommendation lists.
The component displays:
meta: {
layout_wrapper_class: 'inline-grid grid-cols-1 md:grid-cols-3 xl:grid-cols-6 gap-10'
}
This configuration displays product cards in a responsive grid:
Prop | Type | Required | Default | Description |
---|---|---|---|---|
id | string | Yes | - | Unique product identifier |
url | string | Yes | ‘#’ | URL of the product page |
image | string | Yes | - | URL of the main image |
hoverImage | string | No | null | URL of the image displayed on hover |
brand | string | Yes | - | Brand name |
name | string | Yes | - | Product name |
description | string | No | - | Short product description |
color | string | No | - | Text indicating available colors |
webLabel | string | No | - | Price label (e.g., “Web Price”) |
price | string | Yes | - | Product price |
openInNewTab | boolean | No | false | Opens the link in a new tab |
showActionButtons | boolean | No | true | Enables/disables all action buttons |
showFavoriteButton | boolean | No | true | Enables/disables the favorite button |
showQuickViewButton | boolean | No | true | Enables/disables the quick view button |
class | string | No | “” | Additional CSS classes |
context: {
product: {
id: '123',
url: '#',
image: '/img/hero/videoLeft.jpg',
hoverImage: '/img/hero/videoRight.jpg',
brand: 'Brand Name',
name: 'Product Name',
description: 'Product description goes here with some details about the item.',
color: '3 colors available',
webLabel: 'Web Price',
price: '99.99',
openInNewTab: false,
showActionButtons: true,
showFavoriteButton: true,
showQuickViewButton: true,
class: ""
}
}
{% render "@product-card" with { product: product } %}
{% render "@product-card--without-buttons" with { product: product } %}
{% render "@product-card" with {
product: {
...product,
class: "my-custom-class"
}
} %}
Hover Image
hoverImage
is definedhoverImage
, a zoom effect is applied to the main imageAction Buttons
showActionButtons
is trueshowFavoriteButton
and showQuickViewButton
Navigation
openInNewTab