Displaying a grid of video thumbnails that looks good on all screen sizes is a common frontend challenge. Here's the CSS Grid approach I use at DailyWatch, with auto-fill columns, aspect ratio preservation, and mobile optimization.
The HTML Structure
<div class="video-grid">
<article class="video-card">
<a href="/watch/abc123" class="video-card__link">
<div class="video-card__thumb">
<img src="https://i.ytimg.com/vi/abc123/mqdefault.jpg"
alt="Video title here"
loading="lazy"
width="320" height="180">
<span class="video-card__duration">4:32</span>
</div>
<div class="video-card__info">
<h3 class="video-card__title">Amazing Video Title That Might Be Long</h3>
<p class="video-card__channel">Channel Name</p>
<p class="video-card__meta">
<span>1.5M views</span>
<span>2 days ago</span>
</p>
</div>
</a>
</article>
<!-- More cards... -->
</div>
The CSS Grid Layout
.video-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 24px;
padding: 16px;
}
.video-card {
border-radius: 8px;
overflow: hidden;
transition: transform 0.15s ease;
}
.video-card:hover {
transform: translateY(-2px);
}
.video-card__link {
text-decoration: none;
color: inherit;
display: block;
}
.video-card__thumb {
position: relative;
aspect-ratio: 16 / 9;
overflow: hidden;
background: #1a1a2e;
border-radius: 8px;
}
.video-card__thumb img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
.video-card__duration {
position: absolute;
bottom: 8px;
right: 8px;
background: rgba(0, 0, 0, 0.8);
color: #fff;
padding: 2px 6px;
border-radius: 4px;
font-size: 0.75rem;
font-weight: 500;
line-height: 1.4;
}
.video-card__info {
padding: 10px 2px;
}
.video-card__title {
font-size: 0.95rem;
font-weight: 600;
line-height: 1.3;
margin: 0 0 4px;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.video-card__channel {
font-size: 0.85rem;
color: #aaa;
margin: 0 0 2px;
}
.video-card__meta {
font-size: 0.8rem;
color: #888;
margin: 0;
}
.video-card__meta span + span::before {
content: ' \00B7 ';
}
Responsive Breakpoints
/* Mobile: 1 column */
@media (max-width: 480px) {
.video-grid {
grid-template-columns: 1fr;
gap: 16px;
padding: 12px;
}
}
/* Small tablet: 2 columns */
@media (min-width: 481px) and (max-width: 768px) {
.video-grid {
grid-template-columns: repeat(2, 1fr);
gap: 16px;
}
}
/* Large screens: cap at 5 columns */
@media (min-width: 1600px) {
.video-grid {
grid-template-columns: repeat(5, 1fr);
max-width: 1800px;
margin: 0 auto;
}
}
PHP Helper for Generating the Grid
function renderVideoGrid(array $videos, string $class = ''): string {
$html = '<div class="video-grid ' . htmlspecialchars($class) . '">';
foreach ($videos as $video) {
$title = htmlspecialchars($video['title']);
$channel = htmlspecialchars($video['channel_title']);
$thumb = htmlspecialchars($video['thumbnail_url']);
$duration = formatDuration($video['duration'] ?? '');
$views = formatViewCount($video['view_count'] ?? 0);
$timeAgo = timeAgo($video['published_at'] ?? '');
$html .= <<<HTML
<article class="video-card">
<a href="/watch/{$video['video_id']}" class="video-card__link">
<div class="video-card__thumb">
<img src="{$thumb}" alt="{$title}" loading="lazy" width="320" height="180">
<span class="video-card__duration">{$duration}</span>
</div>
<div class="video-card__info">
<h3 class="video-card__title">{$title}</h3>
<p class="video-card__channel">{$channel}</p>
<p class="video-card__meta">
<span>{$views} views</span>
<span>{$timeAgo}</span>
</p>
</div>
</a>
</article>
HTML;
}
$html .= '</div>';
return $html;
}
function formatDuration(string $iso): string {
if (preg_match('/PT(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?/', $iso, $m)) {
$h = (int)($m[1] ?? 0);
$min = (int)($m[2] ?? 0);
$s = (int)($m[3] ?? 0);
if ($h > 0) return sprintf('%d:%02d:%02d', $h, $min, $s);
return sprintf('%d:%02d', $min, $s);
}
return '';
}
function formatViewCount(int $count): string {
if ($count >= 1000000) return round($count / 1000000, 1) . 'M';
if ($count >= 1000) return round($count / 1000, 1) . 'K';
return (string)$count;
}
The key insight with auto-fill + minmax is that the browser automatically calculates the optimal number of columns based on available width. No media queries needed for the basic responsive behavior.
This grid layout powers the video listings on dailywatch.video, automatically adapting from 1 column on phones to 4-5 columns on wide desktops without a single JavaScript calculation.
Top comments (0)