Skip to content

Grid Performance Optimization Guide ​

This guide covers best practices, optimization techniques, and performance monitoring for TachUI's Grid components to ensure optimal performance across all use cases.

Performance Architecture Overview ​

TachUI's Grid system is built for performance with several key optimizations:

  • Native CSS Grid: Leverages browser's optimized layout engine
  • CSS Caching: LRU cache with 95%+ hit rates for repeated configurations
  • Efficient Rendering: Minimal JavaScript overhead with DOM-based updates
  • Smart Defaults: Performance-optimized default configurations
  • Memory Management: Automatic cleanup and garbage collection

Core Optimization Principles ​

1. Choose the Right Grid Component ​

Each Grid component is optimized for specific use cases:

typescript
// ✅ Use Grid for simple, predictable layouts
const SimpleLayout = Grid({
  children: [
    GridRow([Header(), Navigation()]),
    GridRow([Sidebar(), MainContent()])
  ]
})

// ✅ Use LazyVGrid for vertical scrolling with many items  
const ProductCatalog = LazyVGrid({
  columns: [GridItem.adaptive(250, 350)],
  children: products.map(p => ProductCard(p))
})

// ✅ Use LazyHGrid for horizontal scrolling carousels
const ImageCarousel = LazyHGrid({
  rows: [GridItem.fixed(200)],
  children: images.map(img => ImageCard(img))
})

2. Optimize GridItem Configurations ​

GridItem configuration directly impacts performance:

typescript
// ✅ Good: Constrained flexible columns
const OptimizedGrid = LazyVGrid({
  columns: [
    GridItem.flexible(200, 400), // Clear min/max bounds
    GridItem.adaptive(250)       // Reasonable minimum
  ],
  children: items
})

// ❌ Avoid: Unconstrained flexible columns on large datasets
const UnoptimizedGrid = LazyVGrid({
  columns: [
    GridItem.flexible(), // Can cause layout thrashing
    GridItem.flexible()
  ],
  children: thousandsOfItems // Performance issue with large datasets
})

3. Leverage CSS Caching ​

The Grid system automatically caches CSS for repeated configurations:

typescript
// ✅ Reuse common configurations for optimal caching
const commonColumns = [
  GridItem.flexible(200, 300),
  GridItem.flexible(200, 300),
  GridItem.flexible(200, 300)
]

// These grids will reuse cached CSS
const Grid1 = LazyVGrid({ columns: commonColumns, children: items1 })
const Grid2 = LazyVGrid({ columns: commonColumns, children: items2 })
const Grid3 = LazyVGrid({ columns: commonColumns, children: items3 })

// Cache hit rate: ~95% for repeated configurations

Performance Best Practices ​

1. Efficient Item Rendering ​

Optimize individual grid items for better overall performance:

typescript
// ✅ Memoized item components
const MemoizedProductCard = memo(({ product }: { product: Product }) => 
  VStack([
    Image({ src: product.thumbnail, alt: product.name })
      .modifier
      .aspectRatio(1, 'fill')
      .cornerRadius(8)
      .build(),
    
    Text(product.name)
      .modifier
      .fontWeight('bold')
      .lineClamp(2)
      .build()
  ])
  .modifier
  .padding(12)
  .backgroundColor('#ffffff')
  .cornerRadius(8)
  .build()
)

// ✅ Use memoized components in grids
const ProductGrid = LazyVGrid({
  columns: [GridItem.adaptive(250, 350)],
  children: products.map(product => 
    MemoizedProductCard({ product, key: product.id })
  )
})

2. Virtual Scrolling for Large Datasets ​

Implement virtual scrolling for grids with thousands of items:

typescript
// ✅ Virtual scrolling implementation
const VirtualGrid = ({ items }: { items: Item[] }) => {
  const [visibleRange, setVisibleRange] = useState({ start: 0, end: 50 })
  const itemHeight = 300 // Approximate item height
  const containerRef = useRef<HTMLElement>()
  
  const handleScroll = useCallback((event: Event) => {
    const scrollTop = (event.target as HTMLElement).scrollTop
    const viewportHeight = (event.target as HTMLElement).clientHeight
    
    const start = Math.floor(scrollTop / itemHeight) - 5 // Buffer
    const end = Math.ceil((scrollTop + viewportHeight) / itemHeight) + 5
    
    setVisibleRange({ start: Math.max(0, start), end: Math.min(items.length, end) })
  }, [items.length])
  
  const visibleItems = items.slice(visibleRange.start, visibleRange.end)
  
  return LazyVGrid({
    columns: [GridItem.adaptive(250, 350)],
    spacing: 16,
    children: [
      // Spacer for items before visible range
      visibleRange.start > 0 && 
        Spacer().modifier.height(visibleRange.start * itemHeight).build(),
      
      // Visible items
      ...visibleItems.map(item => ItemComponent(item)),
      
      // Spacer for items after visible range
      visibleRange.end < items.length &&
        Spacer().modifier.height((items.length - visibleRange.end) * itemHeight).build()
    ].filter(Boolean)
  })
    .modifier
    .onScroll(handleScroll)
    .build()
}

3. Lazy Loading and Progressive Enhancement ​

Load content progressively to improve initial render performance:

typescript
// ✅ Progressive loading pattern
const ProgressiveGrid = ({ initialItems }: { initialItems: Item[] }) => {
  const [items, setItems] = useState(initialItems)
  const [isLoading, setIsLoading] = useState(false)
  const [hasMore, setHasMore] = useState(true)
  
  const loadMore = useCallback(async () => {
    if (isLoading || !hasMore) return
    
    setIsLoading(true)
    try {
      const newItems = await fetchMoreItems(items.length)
      setItems(prev => [...prev, ...newItems])
      setHasMore(newItems.length === BATCH_SIZE)
    } finally {
      setIsLoading(false)
    }
  }, [items.length, isLoading, hasMore])
  
  // Intersection observer for infinite scroll
  const loadMoreRef = useRef<HTMLElement>()
  
  useEffect(() => {
    const observer = new IntersectionObserver(
      ([entry]) => entry.isIntersecting && loadMore(),
      { threshold: 0.1 }
    )
    
    if (loadMoreRef.current) {
      observer.observe(loadMoreRef.current)
    }
    
    return () => observer.disconnect()
  }, [loadMore])
  
  return LazyVGrid({
    columns: [GridItem.adaptive(250, 350)],
    spacing: 16,
    children: [
      ...items.map(item => ItemComponent(item)),
      
      // Loading indicator
      hasMore && VStack([
        isLoading ? 
          LoadingSpinner() : 
          Text('Load More').modifier.ref(loadMoreRef).build()
      ])
    ]
  })
}

4. Responsive Image Optimization ​

Optimize images within grid items:

typescript
// ✅ Responsive image loading
const OptimizedImageGrid = LazyVGrid({
  columns: [GridItem.adaptive(200, 400)],
  spacing: 12,
  children: images.map(image => 
    Image({
      src: image.src,
      // Responsive image sources
      srcSet: `
        ${image.sizes.small} 200w,
        ${image.sizes.medium} 400w,
        ${image.sizes.large} 600w
      `,
      sizes: `
        (max-width: 600px) 200px,
        (max-width: 1200px) 400px,
        600px
      `,
      alt: image.alt,
      loading: 'lazy', // Native lazy loading
      decoding: 'async'  // Async image decoding
    })
    .modifier
    .aspectRatio(image.aspectRatio || 1, 'fill')
    .cornerRadius(8)
    .build()
  )
})

Animation Performance ​

1. Efficient Animation Configuration ​

Choose appropriate animations for your use case:

typescript
// ✅ Lightweight animations for frequent updates
const FastGrid = LazyVGrid({
  columns: [GridItem.adaptive(250)],
  animations: GridAnimations.smoothLayout(200), // Fast layout changes only
  children: items
})

// ⚠️ Use comprehensive animations sparingly
const HeavyGrid = LazyVGrid({
  columns: [GridItem.adaptive(250)],
  animations: GridAnimations.comprehensive(300, 400), // All animations enabled
  children: items // Use only for small datasets or special cases
})

2. GPU-Accelerated Animations ​

Ensure animations use hardware acceleration:

typescript
// ✅ GPU-accelerated transforms
const GPUOptimizedGrid = LazyVGrid({
  columns: [GridItem.adaptive(250)],
  animations: {
    itemChanges: {
      enter: {
        duration: 250,
        easing: 'ease-out',
        from: 'scale' // Uses transform: scale() - GPU accelerated
      }
    },
    layoutChanges: {
      duration: 300,
      easing: 'ease-out' // CSS Grid changes - browser optimized
    }
  },
  children: items
})

Memory Management ​

1. Automatic Cleanup ​

TachUI Grid components handle cleanup automatically:

typescript
// ✅ Automatic memory management
const GridComponent = ({ items }) => {
  return LazyVGrid({
    columns: [GridItem.adaptive(250)],
    children: items.map(item => ItemComponent(item))
  })
  // Grid automatically cleans up:
  // - Event listeners
  // - CSS cache entries (LRU eviction)
  // - DOM observers
  // - Animation callbacks
}

2. Memory Monitoring ​

Monitor memory usage in development:

typescript
// ✅ Development memory monitoring
const MonitoredGrid = LazyVGrid({
  columns: [GridItem.adaptive(250)],
  children: items,
  // Enable performance monitoring in development
  ...(process.env.NODE_ENV === 'development' && {
    debugLabel: 'ProductGrid',
    performanceMonitoring: true
  })
})

// Monitor performance metrics
if (process.env.NODE_ENV === 'development') {
  // Grid performance metrics are logged to console
  // - Render times
  // - Memory usage
  // - Cache hit rates
  // - Layout calculations
}

Performance Monitoring ​

1. Built-in Performance Tracking ​

TachUI Grid provides built-in performance monitoring:

typescript
import { GridPerformanceMonitor } from '@tachui/core'

// ✅ Enable performance tracking
const monitor = new GridPerformanceMonitor({
  threshold: 16, // Alert if operations take >16ms (60fps target)
  sampleRate: 0.1 // Sample 10% of operations
})

const PerformantGrid = LazyVGrid({
  columns: [GridItem.adaptive(250)],
  children: items,
  performanceMonitor: monitor
})

// Performance data is automatically collected:
// - Layout calculation time
// - Rendering time  
// - CSS cache performance
// - Memory usage patterns

2. Performance Benchmarking ​

Compare different grid configurations:

typescript
// ✅ Benchmark different approaches
const benchmarkGridConfigurations = async () => {
  const items = generateTestItems(1000)
  
  // Configuration A: Fixed columns
  const startA = performance.now()
  const gridA = LazyVGrid({
    columns: [
      GridItem.fixed(250),
      GridItem.fixed(250), 
      GridItem.fixed(250)
    ],
    children: items
  })
  const endA = performance.now()
  
  // Configuration B: Adaptive columns
  const startB = performance.now()
  const gridB = LazyVGrid({
    columns: [GridItem.adaptive(250, 350)],
    children: items
  })
  const endB = performance.now()
  
  console.log(`Fixed columns: ${endA - startA}ms`)
  console.log(`Adaptive columns: ${endB - startB}ms`)
}

Real-World Optimization Examples ​

1. E-commerce Product Grid ​

Optimized for thousands of products:

typescript
const ProductGrid = ({ products }: { products: Product[] }) => {
  // Memoized product card component
  const ProductCard = memo(({ product }: { product: Product }) => (
    VStack([
      Image({ 
        src: product.thumbnail,
        srcSet: `${product.thumbnail} 1x, ${product.thumbnail2x} 2x`,
        alt: product.name,
        loading: 'lazy'
      })
      .modifier.aspectRatio(1, 'fill').cornerRadius(8).build(),
      
      Text(product.name).modifier.fontWeight('bold').lineClamp(2).build(),
      Text(`$${product.price}`).modifier.foregroundColor('#28a745').build()
    ])
    .modifier.padding(12).backgroundColor('#ffffff').cornerRadius(8).build()
  ))
  
  return LazyVGrid({
    columns: [GridItem.adaptive(280, 350)], // Optimal for product cards
    spacing: 16,
    animations: GridAnimations.fadeIn(200), // Subtle entrance
    accessibility: GridAccessibility.basic('Product Grid'),
    children: products.map(product => 
      ProductCard({ product, key: product.id })
    )
  })
    .modifier
    .padding(16)
    .build()
}

2. Social Media Feed ​

Optimized for infinite scroll and varying content heights:

typescript
const SocialFeed = ({ initialPosts }: { initialPosts: Post[] }) => {
  const [posts, setPosts] = useState(initialPosts)
  const [isLoading, setIsLoading] = useState(false)
  
  // Memoized post component with size optimization
  const PostCard = memo(({ post }: { post: Post }) => {
    const [imageLoaded, setImageLoaded] = useState(false)
    
    return VStack([
      // User header
      HStack([
        Image({ 
          src: post.author.avatar, 
          alt: post.author.name,
          loading: 'lazy'
        })
        .modifier.frame(40, 40).cornerRadius(20).build(),
        
        VStack([
          Text(post.author.name).modifier.fontWeight('bold').build(),
          Text(post.timestamp).modifier.fontSize(12).foregroundColor('#666').build()
        ]).modifier.flex(1).marginLeft(12).build()
      ]),
      
      // Content
      Text(post.content).modifier.marginVertical(12).build(),
      
      // Media (if present)
      post.image && VStack([
        // Placeholder while loading
        !imageLoaded && Spacer().modifier.height(200).backgroundColor('#f0f0f0').build(),
        
        Image({ 
          src: post.image, 
          alt: 'Post image',
          loading: 'lazy'
        })
        .modifier
        .aspectRatio(1.5, 'fill')
        .cornerRadius(8)
        .opacity(imageLoaded ? 1 : 0)
        .onLoad(() => setImageLoaded(true))
        .build()
      ]),
      
      // Engagement
      HStack([
        Button({ title: '♥ Like', onTap: () => likePost(post.id) }),
        Button({ title: '💬 Comment', onTap: () => showComments(post.id) }),
        Button({ title: '🔄 Share', onTap: () => sharePost(post.id) })
      ]).modifier.marginTop(12).build()
    ])
    .modifier
    .padding(16)
    .backgroundColor('#ffffff')
    .cornerRadius(12)
    .shadow({ x: 0, y: 2, blur: 4, color: 'rgba(0,0,0,0.1)' })
    .build()
  })
  
  return LazyVGrid({
    columns: [GridItem.adaptive(350, 500)], // Optimal for social content
    spacing: 16,
    animations: GridAnimations.slideIn('up', 200),
    children: posts.map(post => 
      PostCard({ post, key: post.id })
    )
  })
}

3. Dashboard Widget Grid ​

Optimized for real-time updates:

typescript
const DashboardGrid = ({ widgets }: { widgets: Widget[] }) => {
  // Memoize widgets to prevent unnecessary re-renders
  const MemoizedWidget = memo(({ widget }: { widget: Widget }) => {
    return VStack([
      HStack([
        Text(widget.title).modifier.fontWeight('bold').build(),
        Spacer(),
        widget.loading && LoadingSpinner().modifier.frame(16, 16).build()
      ]),
      
      widget.data ? 
        WidgetContent({ data: widget.data, type: widget.type }) :
        SkeletonLoader()
    ])
    .modifier
    .padding(16)
    .backgroundColor('#ffffff')
    .cornerRadius(8)
    .shadow({ x: 0, y: 1, blur: 3, color: 'rgba(0,0,0,0.1)' })
    .build()
  })
  
  return LazyVGrid({
    columns: [
      GridItem.adaptive(300, 400) // Flexible widget sizing
    ],
    spacing: 16,
    // Minimal animations for frequent updates
    animations: GridAnimations.smoothLayout(150),
    children: widgets.map(widget => 
      MemoizedWidget({ widget, key: widget.id })
    )
  })
}

Performance Testing ​

1. Load Testing ​

Test grid performance with various datasets:

typescript
// Performance test suite
const performanceTests = {
  async testSmallDataset() {
    const items = generateItems(50)
    const start = performance.now()
    
    const grid = LazyVGrid({
      columns: [GridItem.adaptive(250)],
      children: items.map(item => ItemComponent(item))
    })
    
    const end = performance.now()
    console.log(`Small dataset (50 items): ${end - start}ms`)
  },
  
  async testLargeDataset() {
    const items = generateItems(5000)
    const start = performance.now()
    
    const grid = LazyVGrid({
      columns: [GridItem.adaptive(250)],
      children: items.slice(0, 100).map(item => ItemComponent(item)) // Virtual scrolling
    })
    
    const end = performance.now()
    console.log(`Large dataset (5000 items, 100 rendered): ${end - start}ms`)
  },
  
  async testComplexItems() {
    const items = generateComplexItems(100)
    const start = performance.now()
    
    const grid = LazyVGrid({
      columns: [GridItem.adaptive(300, 400)],
      children: items.map(item => ComplexItemComponent(item))
    })
    
    const end = performance.now()
    console.log(`Complex items (100 items): ${end - start}ms`)
  }
}

2. Memory Profiling ​

Monitor memory usage over time:

typescript
const ProfiledGrid = ({ items }) => {
  useEffect(() => {
    if (process.env.NODE_ENV === 'development') {
      const memoryMonitor = setInterval(() => {
        if (performance.memory) {
          console.log('Memory usage:', {
            used: Math.round(performance.memory.usedJSHeapSize / 1024 / 1024) + 'MB',
            total: Math.round(performance.memory.totalJSHeapSize / 1024 / 1024) + 'MB',
            limit: Math.round(performance.memory.jsHeapSizeLimit / 1024 / 1024) + 'MB'
          })
        }
      }, 5000)
      
      return () => clearInterval(memoryMonitor)
    }
  }, [])
  
  return LazyVGrid({
    columns: [GridItem.adaptive(250)],
    children: items
  })
}

By following these performance best practices, your TachUI Grid components will deliver optimal performance across all devices and use cases, maintaining smooth 60fps interactions even with large datasets and complex layouts.

Released under the MPL-2.0 License.