column-fitter
v15.0.9
Published
This is an Angular Module containing Components/Services using Material
Readme
Column Fitter Component
Overview
The column-fitter library provides a responsive grid layout system that automatically adjusts the number of columns based on the detected device size. It integrates with the screen-observer package to monitor device changes and dynamically updates CSS Grid layouts for optimal viewing across different screen sizes.
Core Capabilities
📱 Responsive Grid Layout System
- Device Detection: Automatically detects device type using screen-observer service
- Dynamic Column Adjustment: Updates column count based on current device (mobile, tablet, mini, desktop)
- CSS Grid Integration: Uses modern CSS Grid with auto-fit and minmax for flexible layouts
- Flexible Configuration: Support for both fixed columns and device-specific column settings
- Responsive Behavior: Seamlessly adapts between different screen sizes
- Performance Optimized: Uses RxJS distinctUntilChanged to prevent unnecessary updates
🔧 Features
✅ Device Size Detection - Automatic detection via screen-observer integration
✅ Dynamic Grid Updates - Real-time column count adjustment
✅ CSS Grid Foundation - Modern CSS Grid with repeat() and auto-fit
✅ Flexible Configuration - Fixed numbers or device-specific settings
✅ Customizable Styling - Configurable gap, margins, padding, and colors
✅ Performance Optimized - Efficient change detection and updates
✅ Type-Safe Configuration - Strong typing with Column and DeviceSizes models
✅ Demo Component - Interactive demo showcasing all features
Key Benefits
| Feature | Description | |---------|-------------| | Automatic Responsiveness | No manual media queries needed | | Device-Aware Layouts | Optimized layouts for each device type | | Modern CSS Grid | Leverages CSS Grid for superior performance | | Type-Safe Configuration | Full TypeScript support with device models | | Seamless Integration | Works with existing screen-observer implementations |
Demo Component (ColumnFitterDemoComponent)
The demo component showcases responsive grid layouts using a bookmarks list example.
Usage
To use the demo component in your application:
<app-column-fitter-demo></app-column-fitter-demo>Demo Features
- Bookmarks List: Displays a list of classic books in responsive grid
- Device-Specific Columns:
- Mobile: 1 column
- Tablet: 4 columns
- Mini: 2 columns
- Desktop: Auto-fit with minmax
- Real-time Updates: Grid layout updates as you resize the browser
- Visual Feedback: Console logging of device changes
Summary
The column-fitter library provides a modern, responsive grid system that automatically adapts column layouts based on device detection, making it perfect for creating responsive applications without manual media query management.
Quick Start Guide
Installation & Setup (2 minutes)
1. Import Module
// app.module.ts
import { ColumnFitterModule } from 'column-fitter';
@NgModule({
imports: [
ColumnFitterModule
]
})
export class AppModule { }2. Dependencies
The package requires the screen-observer package for device detection:
npm install screen-observerQuick Examples
Example 1: Fixed Column Layout
import { Component } from '@angular/core';
@Component({
selector: 'app-fixed-grid',
template: `
<app-column-fitter
[columns]="4"
[gap]="'1rem'"
[padding]="'1rem'"
[backgroundColor]="'#f5f5f5'">
<div class="grid-item" *ngFor="let item of items">
{{ item.name }}
</div>
</app-column-fitter>
`
})
export class FixedGridComponent {
items = [
{ id: 1, name: 'Item 1' },
{ id: 2, name: 'Item 2' },
{ id: 3, name: 'Item 3' },
{ id: 4, name: 'Item 4' },
{ id: 5, name: 'Item 5' },
{ id: 6, name: 'Item 6' }
];
}Example 2: Device-Specific Columns
import { Component } from '@angular/core';
import { Column, DeviceSizes } from 'column-fitter';
@Component({
selector: 'app-responsive-grid',
template: `
<app-column-fitter
[columns]="responsiveColumns"
[gap]="'1.5rem'"
[margin]="'1rem'"
[minWidth]="'250px'"
[backgroundColor]="'#ffffff'"
[padding]="'1rem'">
<div class="product-card" *ngFor="let product of products">
<h3>{{ product.name }}</h3>
<p>{{ product.description }}</p>
<span class="price">{{ product.price | currency }}</span>
</div>
</app-column-fitter>
`,
styles: [`
.product-card {
padding: 1rem;
border: 1px solid #ddd;
border-radius: 8px;
background: white;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.price {
font-weight: bold;
color: #2196f3;
}
`]
})
export class ResponsiveGridComponent {
responsiveColumns: Column[] = [
{ device: DeviceSizes.MOBILE, columns: 1 },
{ device: DeviceSizes.MINI, columns: 2 },
{ device: DeviceSizes.TABLET, columns: 3 },
{ device: DeviceSizes.DESKTOP, columns: 4 }
];
products = [
{ id: 1, name: 'Product 1', description: 'Description 1', price: 29.99 },
{ id: 2, name: 'Product 2', description: 'Description 2', price: 39.99 },
{ id: 3, name: 'Product 3', description: 'Description 3', price: 49.99 },
{ id: 4, name: 'Product 4', description: 'Description 4', price: 59.99 },
{ id: 5, name: 'Product 5', description: 'Description 5', price: 69.99 },
{ id: 6, name: 'Product 6', description: 'Description 6', price: 79.99 }
];
}Example 3: Gallery Layout
import { Component } from '@angular/core';
import { Column, DeviceSizes } from 'column-fitter';
@Component({
selector: 'app-image-gallery',
template: `
<app-column-fitter
[columns]="galleryColumns"
[gap]="'0.5rem'"
[padding]="'0.5rem'"
[backgroundColor]="'#000'"
[minWidth]="'200px'">
<div class="gallery-item" *ngFor="let image of images">
<img [src]="image.url" [alt]="image.alt" />
<div class="overlay">
<h4>{{ image.title }}</h4>
</div>
</div>
</app-column-fitter>
`,
styles: [`
.gallery-item {
position: relative;
overflow: hidden;
border-radius: 4px;
aspect-ratio: 1;
}
.gallery-item img {
width: 100%;
height: 100%;
object-fit: cover;
transition: transform 0.3s ease;
}
.gallery-item:hover img {
transform: scale(1.1);
}
.overlay {
position: absolute;
bottom: 0;
left: 0;
right: 0;
background: linear-gradient(transparent, rgba(0,0,0,0.8));
color: white;
padding: 1rem;
transform: translateY(100%);
transition: transform 0.3s ease;
}
.gallery-item:hover .overlay {
transform: translateY(0);
}
`]
})
export class ImageGalleryComponent {
galleryColumns: Column[] = [
{ device: DeviceSizes.MOBILE, columns: 2 },
{ device: DeviceSizes.MINI, columns: 3 },
{ device: DeviceSizes.TABLET, columns: 4 },
{ device: DeviceSizes.DESKTOP, columns: 6 }
];
images = [
{ id: 1, url: 'https://picsum.photos/300/300?random=1', alt: 'Random 1', title: 'Image 1' },
{ id: 2, url: 'https://picsum.photos/300/300?random=2', alt: 'Random 2', title: 'Image 2' },
{ id: 3, url: 'https://picsum.photos/300/300?random=3', alt: 'Random 3', title: 'Image 3' },
{ id: 4, url: 'https://picsum.photos/300/300?random=4', alt: 'Random 4', title: 'Image 4' },
{ id: 5, url: 'https://picsum.photos/300/300?random=5', alt: 'Random 5', title: 'Image 5' },
{ id: 6, url: 'https://picsum.photos/300/300?random=6', alt: 'Random 6', title: 'Image 6' }
];
}Example 4: Dashboard Cards
import { Component } from '@angular/core';
import { Column, DeviceSizes } from 'column-fitter';
@Component({
selector: 'app-dashboard',
template: `
<div class="dashboard-container">
<h2>Dashboard</h2>
<app-column-fitter
[columns]="dashboardColumns"
[gap]="'1rem'"
[padding'"
[background]="'1remColor]="'#f8f9fa'"
[minWidth]="'300px'">
<div class="stat-card" *ngFor="let stat of statistics">
<div class="stat-icon">
<mat-icon>{{ stat.icon }}</mat-icon>
</div>
<div class="stat-content">
<h3>{{ stat.value }}</h3>
<p>{{ stat.label }}</p>
</div>
</div>
</app-column-fitter>
</div>
`,
styles: [`
.dashboard-container {
padding: 2rem;
}
.dashboard-container h2 {
margin-bottom: 2rem;
color: #333;
}
.stat-card {
background: white;
padding: 1.5rem;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
display: flex;
align-items: center;
gap: 1rem;
}
.stat-icon {
width: 48px;
height: 48px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
background: #e3f2fd;
color: #1976d2;
}
.stat-content h3 {
margin: 0;
font-size: 1.5rem;
font-weight: bold;
color: #333;
}
.stat-content p {
margin: 0;
color: #666;
font-size: 0.9rem;
}
`]
})
export class DashboardComponent {
dashboardColumns: Column[] = [
{ device: DeviceSizes.MOBILE, columns: 1 },
{ device: DeviceSizes.TABLET, columns: 2 },
{ device: DeviceSizes.MINI, columns: 2 },
{ device: DeviceSizes.DESKTOP, columns: 4 }
];
statistics = [
{ id: 1, icon: 'people', value: '1,234', label: 'Total Users' },
{ id: 2, icon: 'shopping_cart', value: '$12,345', label: 'Revenue' },
{ id: 3, icon: 'trending_up', value: '98.5%', label: 'Growth Rate' },
{ id: 4, icon: 'assignment', value: '567', label: 'Tasks Completed' },
{ id: 5, icon: 'star', value: '4.8/5', label: 'Customer Rating' },
{ id: 6, icon: 'notifications', value: '23', label: 'Pending Alerts' }
];
}Component API
Inputs
| Input | Type | Description | Default |
| :--- | :--- | :--- | :--- |
| padding | string | Padding for the grid container (CSS padding value) | '' |
| margin | string | Margin for the grid container (CSS margin value) | '' |
| backgroundColor | string | Background color for the grid container | '' |
| minWidth | string | Minimum width for auto-fit columns (CSS length value) | '' |
| gap | string | Gap between grid items (CSS gap value) | '1rem' |
| columns | number \| Column[] | Column configuration - fixed number or device-specific array | 0 |
Dynamic Properties
| Property | Type | Description |
|----------|------|-------------|
| gridColumns | string | Current CSS grid-template-columns value |
| hasColumns | boolean | Whether valid column configuration exists |
| subscriptions | Subscription | RxJS subscription management |
Model Structures
DeviceSizes Enum
export enum DeviceSizes {
DESKTOP = 'desktop', // Desktop/large screens
TABLET = 'tablet', // Tablet devices
MINI = 'mini', // Small tablets/large phones
MOBILE = 'mobile' // Mobile phones
}Column Interface
export interface ColumnInterface {
device: DeviceSizes; // Target device type
columns: number; // Number of columns for this device
}Column Class
export class Column implements ColumnInterface {
constructor(
public device = DeviceSizes.DESKTOP,
public columns = 0,
) {}
static adapt(item?: any): Column {
return new Column(
item?.device,
item?.columns
);
}
}Usage Examples
// Device-specific column configurations
const responsiveColumns: Column[] = [
new Column(DeviceSizes.MOBILE, 1), // 1 column on mobile
new Column(DeviceSizes.MINI, 2), // 2 columns on mini devices
new Column(DeviceSizes.TABLET, 3), // 3 columns on tablets
new Column(DeviceSizes.DESKTOP, 4) // 4 columns on desktop
];
// Using adapt method
const adaptedColumns = [
Column.adapt({ device: DeviceSizes.MOBILE, columns: 1 }),
Column.adapt({ device: DeviceSizes.TABLET, columns: 3 })
];
// Mixed configuration
const mixedColumns: (number | Column[])[] = [
3, // Fixed 3 columns for all devices
// OR
[
{ device: DeviceSizes.MOBILE, columns: 1 },
{ device: DeviceSizes.TABLET, columns: 2 },
{ device: DeviceSizes.DESKTOP, columns: 4 }
]
];Grid Layout Logic
CSS Grid Generation
The component automatically generates CSS Grid templates based on the configuration:
Fixed Column Mode
// Input: columns = 3
// Output: gridColumns = 'repeat(3, 1fr)'
// Input: columns = 0 (disabled)
// Output: gridColumns = 'repeat(auto-fit, minmax(250px, 1fr))'Device-Specific Mode
// Device detection logic
if (device === 'desktop' && found(DeviceSizes.DESKTOP)) {
const cols = found(DeviceSizes.DESKTOP) as Column;
return `repeat(${cols.columns}, 1fr)`;
}
// Fallback for unmatched devices
return `repeat(auto-fit, minmax(${this.minWidth}, 1fr))`;Device Detection Flow
- Screen Observer Integration: Subscribes to
screenObserverService.device$ - Change Detection: Uses RxJS
distinctUntilChanged()to prevent unnecessary updates - Column Calculation: Calls
getGridTemplateColumns()with current device - Grid Update: Updates
gridColumnsproperty with new CSS Grid value
Module Configuration
ColumnFitterModule
No Global Configuration Required
The ColumnFitterModule does not provide a forRoot() method or global configuration options. All configuration is done at the component level through input properties.
Module Structure
@NgModule({
declarations: [
ColumnFitterComponent,
ColumnFitterDemoComponent
],
imports: [
// Dependencies are imported by the consuming application
// screen-observer must be installed separately
],
exports: [
ColumnFitterComponent,
ColumnFitterDemoComponent
]
})
export class ColumnFitterModule { }Dependencies
- screen-observer: Device detection service (must be installed separately)
- @angular/core: Core Angular functionality
- rxjs: Reactive programming utilities for device change detection
Styling and Customization
CSS Grid Styling
The component uses CSS Grid with the following base styles:
:host {
display: block;
}
.grid-container {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 1rem;
}Custom Styling Examples
Custom Grid Appearance
// Enhanced grid styling
:host ::ng-deep .grid-container {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 12px;
padding: 2rem;
.grid-item {
background: white;
border-radius: 8px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
transition: transform 0.2s ease, box-shadow 0.2s ease;
&:hover {
transform: translateY(-2px);
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
}
}
}Responsive Gap Adjustment
// Dynamic gaps based on device
:host ::ng-deep .grid-container {
gap: var(--grid-gap, 1rem);
@media (max-width: 768px) {
--grid-gap: 0.5rem;
}
@media (min-width: 1200px) {
--grid-gap: 1.5rem;
}
}Advanced Layout Patterns
Masonry-Style Layout
// CSS Grid with masonry-like behavior
.masonry-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
grid-auto-rows: 200px;
gap: 1rem;
.masonry-item {
grid-row-end: span var(--row-span, 1);
&.large {
--row-span: 2;
}
&.wide {
grid-column-end: span 2;
}
}
}Card-Based Layout
// Card layout with consistent height
.card-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 1.5rem;
align-items: stretch;
.card {
display: flex;
flex-direction: column;
height: 100%;
background: white;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
.card-image {
height: 200px;
overflow: hidden;
img {
width: 100%;
height: 100%;
object-fit: cover;
}
}
.card-content {
padding: 1.5rem;
flex: 1;
display: flex;
flex-direction: column;
}
}
}Integration Examples
With Angular Material
import { Component } from '@angular/core';
import { Column, DeviceSizes } from 'column-fitter';
@Component({
selector: 'app-material-grid',
template: `
<app-column-fitter
[columns]="materialColumns"
[gap]="'1rem'"
[padding]="'1rem'">
<mat-card class="grid-card" *ngFor="let item of materialItems">
<mat-card-header>
<mat-card-title>{{ item.title }}</mat-card-title>
<mat-card-subtitle>{{ item.subtitle }}</mat-card-subtitle>
</mat-card-header>
<img mat-card-image [src]="item.image" [alt]="item.title">
<mat-card-content>
<p>{{ item.description }}</p>
</mat-card-content>
<mat-card-actions>
<button mat-button>LIKE</button>
<button mat-button>SHARE</button>
</mat-card-actions>
</mat-card>
</app-column-fitter>
`
})
export class MaterialGridComponent {
materialColumns: Column[] = [
{ device: DeviceSizes.MOBILE, columns: 1 },
{ device: DeviceSizes.TABLET, columns: 2 },
{ device: DeviceSizes.DESKTOP, columns: 3 }
];
materialItems = [
{
title: 'Card 1',
subtitle: 'Subtitle 1',
description: 'Description for card 1',
image: 'https://picsum.photos/400/200?random=1'
},
// ... more items
];
}With Dynamic Content
import { Component } from '@angular/core';
import { Column, DeviceSizes } from 'column-fitter';
@Component({
selector: 'app-dynamic-content',
template: `
<div class="controls">
<button (click)="addItem()">Add Item</button>
<button (click)="removeItem()">Remove Item</button>
<select [(ngModel)]="selectedLayout" (change)="changeLayout()">
<option value="mobile1">Mobile: 1 Col</option>
<option value="tablet3">Tablet: 3 Col</option>
<option value="desktop4">Desktop: 4 Col</option>
</select>
</div>
<app-column-fitter
[columns]="currentColumns"
[gap]="'1rem'"
[padding]="'1rem'">
<div class="dynamic-item" *ngFor="let item of dynamicItems; trackBy: trackById">
<h3>{{ item.title }}</h3>
<p>{{ item.content }}</p>
<small>ID: {{ item.id }}</small>
</div>
</app-column-fitter>
`
})
export class DynamicContentComponent {
currentColumns: Column[] = [
{ device: DeviceSizes.MOBILE, columns: 1 },
{ device: DeviceSizes.TABLET, columns: 3 },
{ device: DeviceSizes.DESKTOP, columns: 4 }
];
selectedLayout = 'tablet3';
dynamicItems = [
{ id: 1, title: 'Item 1', content: 'Content 1' },
{ id: 2, title: 'Item 2', content: 'Content 2' },
{ id: 3, title: 'Item 3', content: 'Content 3' }
];
addItem() {
const newItem = {
id: Date.now(),
title: `Item ${this.dynamicItems.length + 1}`,
content: `Content ${this.dynamicItems.length + 1}`
};
this.dynamicItems = [...this.dynamicItems, newItem];
}
removeItem() {
if (this.dynamicItems.length > 0) {
this.dynamicItems = this.dynamicItems.slice(0, -1);
}
}
changeLayout() {
switch (this.selectedLayout) {
case 'mobile1':
this.currentColumns = [
{ device: DeviceSizes.MOBILE, columns: 1 },
{ device: DeviceSizes.TABLET, columns: 1 },
{ device: DeviceSizes.DESKTOP, columns: 1 }
];
break;
case 'tablet3':
this.currentColumns = [
{ device: DeviceSizes.MOBILE, columns: 1 },
{ device: DeviceSizes.TABLET, columns: 3 },
{ device: DeviceSizes.DESKTOP, columns: 3 }
];
break;
case 'desktop4':
this.currentColumns = [
{ device: DeviceSizes.MOBILE, columns: 1 },
{ device: DeviceSizes.TABLET, columns: 2 },
{ device: DeviceSizes.DESKTOP, columns: 4 }
];
break;
}
}
trackById(index: number, item: any): any {
return item.id;
}
}Performance Optimization
Change Detection
The component uses several performance optimizations:
- distinctUntilChanged(): Prevents duplicate device updates
- Efficient Grid Calculation: Only recalculates when device actually changes
- Subscription Management: Properly cleans up RxJS subscriptions
Memory Management
ngOnDestroy() {
// Clean up subscriptions to prevent memory leaks
this.subscriptions.unsubscribe();
}Large Dataset Handling
// Use trackBy for large lists
@Component({
template: `
<app-column-fitter [columns]="columns">
<div *ngFor="let item of largeDataset; trackBy: trackById">
{{ item.name }}
</div>
</app-column-fitter>
`
})
export class LargeDatasetComponent {
trackById(index: number, item: any): any {
return item.id; // Use unique identifier
}
}Testing
Unit Testing Example
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ColumnFitterComponent } from './column-fitter.component';
import { Column, DeviceSizes } from './models/column.model';
import { ScreenObserverService } from 'screen-observer';
describe('ColumnFitterComponent', () => {
let component: ColumnFitterComponent;
let fixture: ComponentFixture<ColumnFitterComponent>;
let mockScreenObserverService: jasmine.SpyObj<ScreenObserverService>;
beforeEach(async () => {
const screenObserverSpy = jasmine.createSpyObj('ScreenObserverService', ['device$']);
await TestBed.configureTestingModule({
declarations: [ ColumnFitterComponent ],
providers: [
{ provide: ScreenObserverService, useValue: screenObserverSpy }
]
}).compileComponents();
fixture = TestBed.createComponent(ColumnFitterComponent);
component = fixture.componentInstance;
mockScreenObserverService = TestBed.inject(ScreenObserverService) as jasmine.SpyObj<ScreenObserverService>;
});
it('should create', () => {
expect(component).toBeTruthy();
});
it('should set fixed columns correctly', () => {
component.columns = 3;
fixture.detectChanges();
const result = component.getGridTemplateColumns('desktop');
expect(result).toBe('repeat(3, 1fr)');
});
it('should handle device-specific columns', () => {
component.columns = [
{ device: DeviceSizes.MOBILE, columns: 1 },
{ device: DeviceSizes.TABLET, columns: 3 },
{ device: DeviceSizes.DESKTOP, columns: 4 }
];
fixture.detectChanges();
expect(component.getGridTemplateColumns('mobile')).toBe('repeat(1, 1fr)');
expect(component.getGridTemplateColumns('tablet')).toBe('repeat(3, 1fr)');
expect(component.getGridTemplateColumns('desktop')).toBe('repeat(4, 1fr)');
});
it('should fallback to auto-fit when no matching device', () => {
component.columns = [
{ device: DeviceSizes.MOBILE, columns: 1 }
];
component.minWidth = '200px';
fixture.detectChanges();
const result = component.getGridTemplateColumns('desktop');
expect(result).toBe('repeat(auto-fit, minmax(200px, 1fr))');
});
it('should handle disabled state (columns = 0)', () => {
component.columns = 0;
component.minWidth = '250px';
fixture.detectChanges();
const result = component.getGridTemplateColumns('desktop');
expect(result).toBe('repeat(auto-fit, minmax(250px, 1fr))');
});
});Troubleshooting
Common Issues
- No columns showing: Ensure screen-observer package is installed and configured
- Layout not updating: Check that device$ observable is emitting values
- Styling issues: Verify CSS Grid is supported in target browsers
- Performance issues: Consider using OnPush change detection for large datasets
Debug Mode
@Component({
template: `
<div class="debug-info">
Current Device: {{ currentDevice }}<br>
Grid Columns: {{ gridColumns }}<br>
Has Columns: {{ hasColumns }}<br>
Columns Config: {{ columns | json }}
</div>
<app-column-fitter
[columns]="columns"
[gap]="gap"
[minWidth]="minWidth">
<!-- Content -->
</app-column-fitter>
`
})
export class DebugColumnFitterComponent {
currentDevice = '';
gridColumns = '';
hasColumns = false;
columns: any = [];
gap = '1rem';
minWidth = '250px';
constructor() {
// Add debugging logic
}
}Performance Monitoring
ngOnInit() {
const start = performance.now();
this.subscriptions.add(
this.screenObserverService.device$.subscribe((screen: string) => {
const updateStart = performance.now();
this.gridColumns = this.getGridTemplateColumns(screen);
const updateEnd = performance.now();
console.log(`Grid update took ${updateEnd - updateStart}ms for device: ${screen}`);
})
);
const initEnd = performance.now();
console.log(`ColumnFitter initialization took ${initEnd - start}ms`);
}Browser Support
CSS Grid Support
The component requires CSS Grid support, which is available in:
- Chrome: 57+ (March 2017)
- Firefox: 52+ (March 2017)
- Safari: 10.1+ (March 2017)
- Edge: 16+ (October 2017)
Fallback for Older Browsers
// CSS Grid fallback using Flexbox
.grid-container {
display: flex;
flex-wrap: wrap;
margin: -0.5rem;
.grid-item {
flex: 1 1 250px; /* Minimum width of 250px */
margin: 0.5rem;
@supports (display: grid) {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
margin: 0;
}
}
}Progressive Enhancement
// JavaScript fallback for older browsers
if (!CSS.supports('display', 'grid')) {
// Apply Flexbox fallback
this.applyFlexboxFallback();
}