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
In the admin panel, edit any page
Add a Competition Grid block
In the "Competition Card Style" dropdown, select "Custom Twig (Advanced)"
Expand the "Custom Card Template" section
Write your CSS in the CSS Styles field
Write your card HTML + Twig in the Card HTML + Twig Code field
Save the page and preview
Available Variables
Each card receives a product object with the following fields:
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://..."]
Dynamic Components
Countdown Timer
Display a live countdown to the competition end time:
Note: Use
end_date_seconds(notend_date_raw) to avoid timezone issues.
Properties:
time.days- Days remainingtime.hours- Hours (0-23)time.minutes- Minutes (0-59)time.seconds- Seconds (0-59)hasEnded- Boolean, true when countdown reaches zeropad(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 cartadded- True after successful add (resets after 5s)error- True if an error occurrederrorMessage- Error message textrequiresLogin- True if user must login firstcanAdd- Computed: true if add button should be enabled
Methods:
increment()- Increase ticket countdecrement()- Decrease ticket countaddToCart()- 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:
Example 3: Full Featured Card
CSS Styles:
Card HTML:
Tips
Separate CSS from HTML - Put all styles in the CSS field, keep your card HTML clean
Use dynamic components - Countdown timers update live without page refresh
Handle empty values - Use
{{ subtitle|default('') }}for optional fieldsTest on mobile - Cards display in a 2-column grid on mobile, 3 columns on desktop
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.stopon 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, widthraffleQuickAdd(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