For the complete documentation index, see llms.txt. This page is also available as Markdown.

Custom Twig Product Grid

The Competition Grid block's "Custom Twig (Advanced)" layout lets you create fully custom product cards for your competition listings.

Overview

When "Custom Twig (Advanced)" is selected:

  • A CSS Styles editor appears for styling (loaded once per page)

  • A Card HTML + Twig Code editor appears for your card template

  • Each product is rendered using your custom template

  • Dynamic Alpine.js components are available for countdowns and progress bars

How to Use

  1. In the admin panel, edit any page

  2. Add a Competition Grid block

  3. In the "Competition Card Style" dropdown, select "Custom Twig (Advanced)"

  4. Expand the "Custom Card Template" section

  5. Write your CSS in the CSS Styles field

  6. Write your card HTML + Twig in the Card HTML + Twig Code field

  7. Save the page and preview

Available Variables

Each card receives a product object with the following fields:

Field
Type
Description
Example

product.id

int

Product ID

123

product.title

string

Competition name

"Win a BMW M3"

product.subtitle

string

Optional subtitle

"Competition of the Month"

product.slug

string

Product URL slug

"win-a-bmw-m3"

product.url

string

Product page link

"/product/win-a-bmw-m3"

product.image_url

string

Featured image URL

Full URL

product.slider_image_url

string

Slider/hero image URL

Full URL

product.portrait_image_url

string

Portrait image URL

Full URL or null

product.featured_video_url

string

Featured video URL (if uploaded)

Full URL or null

product.video_embed_url

string

Player address for the Video URL, or null

"https://www.youtube-nocookie.com/embed/..."

product.video_embed_type

string

"iframe" (YouTube or Vimeo) or "video" (video file), or null

"iframe"

product.video_embed_mime_type

string

File type for a video file, otherwise null

"video/mp4"

product.video_embed_orientation

string

"portrait" (YouTube Shorts) or "landscape", or null

"landscape"

product.price

string

Current price (formatted)

"£1.99"

product.price_raw

float

Current price (numeric, for calculations)

1.99

product.price_display

string

Pretty current price (shows "Free" or "50p" for small amounts)

"£1.99"

product.original_price

string

Original price if on sale

"£2.99"

product.original_price_raw

float

Original price as a number (for calculations)

2.99

product.is_on_sale

bool

Whether on sale

true/false

product.is_free

bool

Whether the product is free to enter

true/false

product.has_question

bool

Whether product requires question answer

true/false

product.question

string

The entry question text, if any

"What colour is the sky?"

product.cash_alt

string

Cash alternative

"£50,000"

product.cash_alt_raw

float

Cash alternative as a number

50000

product.draw_date

string

Human readable draw date

"Draw On Mon Jan 5th 7:00pm"

product.end_date

string

Human readable end date

"Today 7:00pm"

product.start_date_raw

string

ISO start date (Y-m-d H:i:s)

"2026-01-01 09:00:00"

product.start_date

string

Human readable start date

"Tomorrow 9:00am"

product.start_date_seconds

int

Seconds until the competition opens (0 once open)

0

product.has_started

bool

Whether the start date has passed

true

product.draw_date_raw

string

Draw datetime (Y-m-d H:i:s)

"2026-01-05 19:00:00"

product.end_date_raw

string

End datetime (Y-m-d H:i:s)

"2026-01-05 19:00:00"

product.draw_date_seconds

int

Seconds until draw

86400

product.end_date_seconds

int

Seconds until end (for countdown)

3600

product.max_tickets

int

Total tickets available

5000

product.sold_tickets

int

Tickets sold

3421

product.tickets_remaining

int

Tickets left to sell

1579

product.percent_sold

float

Percentage sold

68.4

product.max_per_user

int

Max tickets per user (0 means no limit)

50

product.instant_wins

int

Number of instant wins

5

product.instant_wins_remaining

int

Remaining instant wins not yet won

3

product.game_type

string

Type of game

"standard"

product.has_ended

bool

Whether competition ended

true/false

product.is_sold_out

bool

Whether the product is sold out

true/false

product.is_active

bool

Whether the product is active

true/false

product.is_featured

bool

Whether the product is in the slider

true/false

product.auto_draw

bool

Whether the draw happens automatically

true/false

product.leaderboard_enabled

bool

Whether the product has a leaderboard

true/false

product.winner_qty

int

Number of winners

1

product.simple_max_ticket

bool

Whether the product shows percentage sold only

true/false

product.category

string

First category name

"Cars"

product.categories

array

All category names

["Cars", "Featured"]

product.description

string

Product description

"Win this amazing..."

product.gallery

array

Array of gallery image URLs

["https://...", "https://..."]

The grid only shows competitions that have reached their start date, so product.has_started is always true here. Scheduled competitions join the grid automatically once they open. To embed a competition's Video URL, see the Video URL Embed example in Custom Twig Block.

Dynamic Components

Countdown Timer

Display a live countdown to the competition end time:

Note: Use end_date_seconds (not end_date_raw) to avoid timezone issues.

Properties:

  • time.days - Days remaining

  • time.hours - Hours (0-23)

  • time.minutes - Minutes (0-59)

  • time.seconds - Seconds (0-59)

  • hasEnded - Boolean, true when countdown reaches zero

  • pad(num) - Zero-pad numbers (e.g., "05")

Progress Bar

Display ticket sales progress:

Properties:

  • percent - The percentage value (0-100)

  • width - Display width (minimum 3% for visibility)

Quick Add to Cart

Add tickets directly to cart without leaving the page. This component handles validation, loading states, and cart updates via AJAX.

Note: Products with questions must use the full product page. The component automatically hides for these products.

Configuration: | Option | Type | Description | |--------|------|-------------| | productId | int | Product ID (required) | | price | float | Price for default ticket calculation (use price_raw) | | maxTickets | int | Maximum tickets allowed per user | | hasQuestion | bool | Whether product requires question answer | | hasEnded | bool | Whether competition has ended |

Properties:

  • tickets - Current ticket quantity (auto-calculated based on price)

  • loading - True while adding to cart

  • added - True after successful add (resets after 5s)

  • error - True if an error occurred

  • errorMessage - Error message text

  • requiresLogin - True if user must login first

  • canAdd - Computed: true if add button should be enabled

Methods:

  • increment() - Increase ticket count

  • decrement() - Decrease ticket count

  • addToCart() - Submit to cart (handles AJAX, toast notifications, Livewire cart sync)

Events Dispatched:

  • cartUpdated - Livewire event to refresh cart components

Default Ticket Calculation: The component automatically sets a sensible default ticket quantity based on price:

  • £20+ → 2 tickets

  • £10-20 → 4 tickets

  • £3-10 → 6 tickets

  • £1-3 → 10 tickets

  • Lower prices → Higher defaults (up to 1000 for £0.01)

Code Examples

Example 1: Simple Card

CSS Styles:

Card HTML:

Example 2: Card with Countdown

CSS Styles:

Card HTML:

CSS Styles:

Card HTML:

Tips

  1. Separate CSS from HTML - Put all styles in the CSS field, keep your card HTML clean

  2. Use dynamic components - Countdown timers update live without page refresh

  3. Handle empty values - Use {{ subtitle|default('') }} for optional fields

  4. Test on mobile - Cards display in a 2-column grid on mobile, 3 columns on desktop

  5. Use end_date_seconds - For countdown timers, always use seconds to avoid timezone issues

Common Patterns

Sale Badge

Conditional Subtitle

Game Type Badge

Multiple Winners

Quick Add Button (Inline)

Tip: Use @click.stop on the Quick Add container to prevent the card link from triggering when clicking the add button.

Example 4: Card with Quick Add

CSS Styles:

Card HTML:


AI Context

For AI Assistants: This documents the RaffleX Competition Grid block's Twig layout for creating custom product cards.

Block Structure:

  • CSS Styles field (loaded once via @pushOnce)

  • Card HTML + Twig field (rendered per product)

  • Each card wrapped in <a href> to product page automatically

Product object fields: id, title, subtitle, slug, url, image_url, featured_video_url, video_embed_url, video_embed_type, video_embed_mime_type, video_embed_orientation, slider_image_url, portrait_image_url, price, price_raw, price_display, original_price, original_price_raw, is_on_sale, is_free, has_question, question, cash_alt, cash_alt_raw, draw_date, end_date, start_date, start_date_raw, start_date_seconds, has_started, draw_date_raw, end_date_raw, draw_date_seconds, end_date_seconds, max_tickets, sold_tickets, tickets_remaining, percent_sold, max_per_user, instant_wins, instant_wins_remaining, game_type, has_ended, is_sold_out, is_active, is_featured, auto_draw, leaderboard_enabled, winner_qty, simple_max_ticket, category, categories, description, gallery

Dynamic Alpine Components:

  • raffleCountdown(seconds) - Live countdown (use end_date_seconds). Props: time.days/hours/minutes/seconds, hasEnded, pad()

  • raffleProgress(percent) - Progress bar. Props: percent, width

  • raffleQuickAdd(config) - Add to cart without page navigation. Config: productId, price, maxTickets, hasQuestion, hasEnded. Props: tickets, loading, added, error, errorMessage, requiresLogin, canAdd. Methods: increment(), decrement(), addToCart()

Grid Layout: 2 columns mobile, 3 columns desktop with gap spacing


See also: Settings Variables for the global settings available in all Twig templates, and the Custom Twig Slider for building a custom hero section.

Last updated