Odds grid widgets are the dominant mechanism for displaying betting odds on sports websites and in betting applications. A well-designed odds grid widget should be invisible to the end user—it loads instantly, updates seamlessly, and displays information clearly. A poorly designed widget creates frustration (slow loading, jarring layout shifts, unreadable data) that damages user experience and directly impacts conversion.
This specification provides technical requirements for production-grade odds grid widgets used in sports betting and publishing contexts.
Widget Purpose and Context
An odds grid widget displays betting odds for a specific match or event in grid format, typically showing:
- Columns: Betting markets (1X2, Over/Under, Asian Handicap, etc.)
- Rows: Outcomes within each market (Home Win, Draw, Away Win for 1X2; Over, Under for Totals)
- Values: Odds in requested format (Decimal, Fractional, American)
Typical implementation sizes: 300-600px width, 150-400px height depending on market count.
Core Performance Requirements
Load Time and Performance Budget
Primary Requirement: Widget must load and render within 500ms of initialization, including:
- Script injection and parsing
- Data fetch from API/data source
- DOM construction and initial render
- CSS styling application
This 500ms requirement assumes widget is embedded on a page that has already loaded. Page rendering may occur before widget loads (recommended pattern).
Critical rendering path:
0ms: Widget script execution starts
50-100ms: API call initiated
100-150ms: Initial DOM elements created
150-200ms: Data received from API
200-300ms: Full DOM construction + CSS application
300-500ms: Final render + interactive state ready
Measurement and Monitoring
Implement performance monitoring using:
- Navigation Timing API: Measure widget load time relative to page load
- Resource Timing API: Measure API call latency and data transfer size
- Web Vitals: Track CLS (Cumulative Layout Shift), LCP (Largest Contentful Paint), FID (First Input Delay)
Target Web Vitals scores:
- LCP: <1.0s
- FID: <100ms
- CLS: <0.1
- TTFB (Time to First Byte): <400ms
Missing these targets by >20% indicates optimisation needed.
Data Transfer Size
Widget bundle size: <50kb (gzip)
- Script code: <30kb
- Initial CSS: <10kb
- Icons/assets: <10kb
API response size: <5kb per odds update
- Use compact JSON format
- Minimize metadata
- Separate rate-limiting headers from payload
Responsive Design and Display Sizes
Device-Specific Layouts
Odds grid widgets must render properly across:
-
Desktop (1200px+): Full grid with all markets visible
- Layout: 6-8 columns × 2-5 rows
- Font size: 14-16px
- Cell padding: 8-12px
-
Tablet (600-1200px): Condensed grid or scrollable view
- Layout: 3-4 columns × 2-5 rows (scrollable)
- Font size: 13-15px
- Cell padding: 6-10px
-
Mobile (0-600px): Single-market view or horizontal scroll
- Layout: 2-3 columns visible at a time, horizontally scrollable
- Font size: 12-14px
- Cell padding: 4-8px
- Touch-friendly minimum tap target: 44px × 44px
Viewport Meta Configuration
<meta name="viewport" content="width=device-width, initial-scale=1.0,
maximum-scale=5.0, user-scalable=yes">
Widget must maintain usability with user zoom (0.5x to 2x).
Scrolling and Overflow Behavior
For widgets with more markets than screen space:
- Desktop: Horizontal scroll within widget boundary (scrollbar visible)
- Tablet: Horizontal scroll with touch-friendly scroll indicators
- Mobile: Horizontal scroll, snap-to-position for better UX
Avoid vertical scrolling in widget (constrains height).
Data Refresh and Real-Time Updates
Update Frequency
Pre-Match Odds (no live event):
- Update interval: 30-60 seconds
- Acceptable latency: <5 seconds from odds change to display update
In-Play / Live Event:
- Update interval: 10-30 seconds
- Acceptable latency: <1 second from odds change to display update
- Critical: Never display stale odds during live events
Update Mechanism
Two patterns are common:
-
Polling Pattern (simpler, more common):
- Call API every 30 seconds (pre-match) or 10 seconds (in-play)
- Simple to implement, compatible with all environments
- Higher server load at scale
-
WebSocket Pattern (more efficient):
- Maintain persistent connection
- Receive odds push updates immediately
- Lower latency, lower server load, more complex client-side
Handling Stale Data
If API latency exceeds thresholds:
- Display "Last updated: X seconds ago" indicator
- Reduce confidence level of displayed odds (lighter styling, warning icon)
- Implement automatic retry on network timeout (3 retries, exponential backoff)
Display Formatting
Odds Format Selection
Support multiple odds formats simultaneously:
-
Decimal (European standard):
- Format: 1.25, 2.50, 5.00
- Precision: 2 decimal places minimum
- Use cases: Europe, UK, Australia
-
Fractional (UK traditional):
- Format: 1/4, 3/2, 4/1
- Reduce fractions (e.g., 2/4 → 1/2)
- Use cases: UK betting exchanges
-
American (Moneyline):
- Format: -400, +150, -200
- Use cases: US betting market
- Interpretation: Negative = amount to win $100; Positive = win on $100
Implementation: Use ISO 4217 for currency format; implement Intl.NumberFormat API for locale-specific formatting.
Cell Styling and Highlighting
Standard cell:
- Background: White or neutral light gray
- Border: 1px solid #ccc
- Text color: #333 (dark gray)
- Font weight: Normal (400)
Highlighted cell (best available odds):
- Background: Light green (#e6f7e6) or light blue (#e6f0f7)
- Font weight: Bold (600)
- Border: 1px solid highlight color
Updated cell animation (on data refresh):
- Flash highlight for 500-1000ms when odds change
- Fade smoothly (not jarring color change)
- Use background-color transition: ease-in-out 200ms
Disabled/Unavailable cell:
- Background: Light gray (#f5f5f5)
- Text: Dimmed (#999)
- Cursor: Not-allowed
- Display: "N/A" or "-"
Decimal Precision
- Odds 1.01-2.00: Display 2 decimal places (1.25)
- Odds 2.01-10.00: Display 2 decimal places (5.50)
- Odds 10.01+: Display 2 decimal places (15.00)
Don't display more than 2 decimal places (e.g., avoid 1.254).
Accessibility Requirements
WCAG 2.1 Level AA Compliance
Odds grid widgets must meet accessibility standards:
-
Keyboard Navigation:
- Tab through cells (implement tabindex)
- Enter key to select odds
- Arrow keys to navigate grid
- Escape to deselect/close
-
Screen Reader Support:
- Use semantic HTML (table, thead, tbody, th, td)
- Provide alt text for icons
- Use aria-labels for column/row headers
- Example: "Home Win odds: 1.50"
-
Color Contrast:
- Text contrast ratio: 4.5:1 minimum (normal text)
- 3:1 minimum for large text (18px+)
- Don't rely solely on color to convey information (use labels)
-
Focus Indicators:
- Visible focus outline on all interactive elements
- Focus color: Contrasting color (e.g., #0066cc)
- Focus outline width: 2-3px
Example HTML Structure
<table role="grid" aria-label="Match odds for Manchester United vs Liverpool">
<thead>
<tr>
<th id="market-1x2">1X2</th>
<th id="market-totals">Over/Under 2.5</th>
</tr>
</thead>
<tbody>
<tr>
<td headers="market-1x2">
<button aria-label="Home win at 1.50">1.50</button>
</td>
<td headers="market-totals">
<button aria-label="Over 2.5 goals at 1.90">1.90</button>
</td>
</tr>
</tbody>
</table>
Mobile Optimisation
Touch Interactions
- Tap target size: Minimum 44×44px (Apple guideline)
- Touch feedback: Visual feedback (opacity change or color) within 100ms
- Long press: Option to view odds details (historical data, etc.)
Mobile-Specific Features
-
Gesture support:
- Horizontal swipe to scroll markets
- Pinch-to-zoom support (with reasonable limits)
- Double-tap to select/deselect
-
Mobile data optimisation:
- Lazy-load images (icons, logos)
- Aggressive compression (gzip + Brotli)
- Implement data saver mode (reduce update frequency if enabled)
-
Connectivity handling:
- Detect offline status (navigator.onLine)
- Display cached odds if offline (with clear indication)
- Auto-reconnect with exponential backoff
Security and Compliance
Data Validation
Validate all API responses:
// Example validation
const validateOdds = (odds) => {
if (typeof odds !== 'number') throw new Error('Invalid odds format');
if (odds < 1.01 || odds > 1000) throw new Error('Odds out of range');
if (!Number.isFinite(odds)) throw new Error('Non-finite odds');
return true;
};
- Validate odds are numeric and within reasonable range (1.01-1000)
- Validate market identifiers
- Validate timestamps (ensure freshness)
Content Security Policy (CSP)
Implement CSP headers to prevent XSS:
Content-Security-Policy:
script-src 'self' https://api.example.com;
style-src 'self' 'unsafe-inline';
img-src 'self' https:;
connect-src 'self' https://api.example.com
GDPR and Privacy
- No persistent storage of odds without user consent
- Don't track betting behavior across sites without disclosure
- Implement Do Not Track (DNT) header respect
- Provide clear privacy policy for widget data usage
Responsible Gambling
Display responsible gambling messaging:
<div class="rg-warning" role="complementary">
<p>⚠️ Betting involves risk of loss.
See www.begambleaware.org</p>
</div>
- Display problem gambling hotline number
- Add age verification if required by jurisdiction
- Implement loss-limit warnings if tracking customer activity
Testing and Quality Assurance
Automated Testing
- Unit tests: Odds formatting, data validation (Jest/Mocha)
- Integration tests: API communication, data updates (Cypress/Playwright)
- Performance tests: Load time, update latency (Lighthouse, WebPageTest)
- Accessibility tests: WCAG compliance (axe DevTools, WAVE)
Browser and Device Coverage
Test across:
- Browsers: Chrome, Firefox, Safari, Edge (latest + 1 version back)
- Devices: iPhone, Android, iPad, desktop (1920×1080, 1366×768, mobile viewports)
- Network conditions: 4G, 3G, offline simulation (Chrome DevTools throttling)
Performance Benchmarking
Establish baseline metrics:
Load time: 350ms p50, 500ms p95
Update latency: 100ms p50, 300ms p95
Interaction latency: <100ms
Memory usage: <15MB
CPU usage: <5% during updates
Monitor continuously and alert if metrics exceed thresholds.
Common Implementation Mistakes to Avoid
- Blocking page load: Don't load widget synchronously; always async
- Layout shift on updates: Use fixed-size cells to prevent CLS
- Missing fallback: Display reasonable fallback if API fails
- No error handling: Implement try-catch around API calls
- Hardcoded styling: Use CSS variables for theming flexibility
- No mobile optimisation: Odds grid must be usable on mobile
- Stale odds: Always show when odds were last updated
- Poor accessibility: Implement keyboard navigation and screen reader support
- No performance monitoring: Can't improve what you don't measure
- Over-engineering: Simpler is better (avoid unnecessary animations, transitions)
Widget Size and Layout Options
Different use cases require different widget dimensions:
Standard Desktop Widget (600×300px)
Best for: Publisher sidebar, operator promotions Layout: 6-8 columns × 3-4 rows
- Width: 600px
- Height: 300px
- Market count: 6-12 distinct markets
- Typical markets: 1X2, Totals, Handicaps, 2-3 props
CSS example:
.odds-widget-standard {
width: 600px;
height: 300px;
overflow-y: auto;
border: 1px solid #ddd;
border-radius: 4px;
}
.odds-widget-standard .market {
display: grid;
grid-template-columns: repeat(6, 1fr);
gap: 8px;
padding: 8px;
}
Compact Widget (300×200px)
Best for: Inline article embeds, limited space Layout: 3-4 columns × 2-3 rows (with horizontal scroll)
- Width: 300px
- Height: 200px
- Market count: 3-6 distinct markets
- Typical markets: 1X2, Totals only
Full-Width Widget (100%×variable)
Best for: Operator dashboard, comprehensive odds display Layout: All markets visible, multi-row
- Width: 100% of container
- Height: Dynamic based on market count
- Market count: 15+ distinct markets
- All available markets displayed
Mobile-Optimised Widget (responsive 0-600px)
Best for: Mobile operators, publisher mobile experience Layout: 2-column mobile, 4-column tablet, 6+ desktop
- Width: 100% (responsive)
- Height: 200px mobile, 250px tablet, 300px desktop
- Touch-friendly: 44×44px minimum tap targets
Ticker Widget (narrow, scrolling)
Best for: Continuous display of odds changes Layout: Single column, wide, horizontal scroll
- Width: 1200px or 100%
- Height: 60px
- Shows: Market name + current odds, scrolls through all matches
- Auto-scroll: New matches appear from right
Advanced Implementation Patterns
Pattern 1: Skinning and Theme Customization
Enable flexible styling for different contexts (publisher vs. operator site):
/* CSS Custom Properties for theming */
:root {
--odds-bg: #ffffff;
--odds-text: #333333;
--odds-border: #cccccc;
--odds-highlight: #d4edda;
--odds-font-size: 14px;
--odds-padding: 8px;
}
/* Dark mode variant */
[data-theme="dark"] {
--odds-bg: #2d2d2d;
--odds-text: #ffffff;
--odds-border: #444444;
--odds-highlight: #1e6622;
}
/* Compact variant */
[data-layout="compact"] {
--odds-font-size: 12px;
--odds-padding: 4px;
}
This approach lets same widget display differently based on context without code changes.
Pattern 2: Virtual Scrolling for Large Tables
For widgets with hundreds of markets:
class VirtualOddsGrid {
constructor(container, allOdds, visibleRows) {
this.container = container;
this.allOdds = allOdds;
this.visibleRows = visibleRows;
this.scrollTop = 0;
this.render();
this.container.addEventListener('scroll', () => this.onScroll());
}
onScroll() {
const newScrollTop = this.container.scrollTop;
if (Math.abs(newScrollTop - this.scrollTop) > 30) { // Only re-render on significant scroll
this.scrollTop = newScrollTop;
this.render();
}
}
render() {
const startIndex = Math.floor(this.scrollTop / rowHeight);
const endIndex = startIndex + this.visibleRows;
const visibleOdds = this.allOdds.slice(startIndex, endIndex);
// Render only visible rows, not entire table
this.container.innerHTML = this.buildHTML(visibleOdds, startIndex);
}
}
Virtual scrolling reduces DOM nodes from 1000+ to 20-30, dramatically improving performance.
Pattern 3: Optimistic Updates
Update UI immediately when user interacts, verify with server:
function selectOdds(oddsId, odds) {
// Optimistic: Update UI immediately
updateUIselection(oddsId);
// Pessimistic: Verify with server
api.validateOdds(oddsId, odds).then(response => {
if (!response.valid) {
// Odds have changed, revert UI
revertSelection();
showMessage('Odds changed, try again');
}
}).catch(() => {
revertSelection();
showNetworkError();
});
}
This pattern makes UI feel responsive even with network latency.
Troubleshooting Common Widget Issues
Issue: Widget loads but odds don't update
Likely causes:
- API endpoint misconfigured (404 errors)
- Data refresh interval is too long or not triggered
- CORS policy blocking API calls
Diagnostic steps:
- Check browser console for JavaScript errors
- Check Network tab in DevTools for failed API requests
- Verify API endpoint is correct
- Check Access-Control-Allow-Origin headers
Issue: Widget updates create layout shift
Likely causes:
- Odds change cell size (e.g., 1.50 → 12.50 changes width)
- Font-size changes on update
- Padding/margin changes
Solution:
.odds-cell {
width: 60px; /* Fixed width prevents shift */
height: 40px; /* Fixed height prevents shift */
overflow: hidden;
text-align: center;
}
Issue: Mobile widget shows misaligned text
Likely causes:
- Font size too large for small screen
- Cell padding creates overflow
- Viewport meta tag missing
Solution:
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- Media query for mobile -->
@media (max-width: 600px) {
.odds-cell {
font-size: 12px;
padding: 4px;
}
}
Testing Checklist
Before deploying widget to production:
Functional Testing:
- Widget loads within 500ms
- Odds update correctly when data changes
- All responsive breakpoints work (mobile, tablet, desktop)
- Clicking odds updates betslip (if applicable)
- Keyboard navigation works (Tab, Enter, Arrow keys)
- Screen reader announces odds correctly
Performance Testing:
- LCP < 1.0s
- FID < 100ms
- CLS < 0.1
- Widget doesn't block page load
- Memory usage stays <15MB
Cross-browser Testing:
- Chrome/Edge (latest + 1 version back)
- Firefox (latest + 1 version back)
- Safari (latest + 1 version back)
- Mobile Safari (iPhone)
- Chrome Mobile (Android)
Accessibility Testing:
- Color contrast > 4.5:1
- Keyboard navigation works
- Screen reader testing (NVDA, JAWS)
- No flash or seizure triggers
- Focus indicators visible
Conclusion
Odds grid widgets are deceptively complex—simple to look at, challenging to implement well. The core requirements are straightforward: load fast (<500ms), update reliably, display accessibly, work on all devices, and maintain visual stability.
The difference between a good implementation and a poor one is often invisible to end users until something goes wrong—slow loading, jarring updates, accessibility issues. Invest time in meeting the performance, accessibility, and responsive requirements outlined here. The competitive operators will have the fastest, most reliable widgets.
CTA: Widget Implementation Resources
Download the Odds Grid Widget Implementation Checklist for a step-by-step validation against this specification.
[Download Checklist]
Or access the widget code samples showing example implementations of critical patterns (responsive layout, real-time updates, accessibility compliance).
[View Code Samples]
Last updated: March 2026. Based on WCAG 2.1, Web Vitals Core Standards, and production implementations. © 2026 FairPlay Sports Media.
Ready to explore BetTech for your business?
Talk to the FairPlay team about how our platform can work for your business.
Get Started








