Blog Ecommerce 7 min read

Restaurant Menu and Dish Photo Fallbacks

Keep restaurant menus and food delivery apps polished with restaurant menu placeholder images that hold layout when dish photos are missing or fail to load.

restaurant menu placeholderfood delivery image fallbackmenu photo missingimage fallbackecommerce UX
Restaurant Menu and Dish Photo Fallbacks

Restaurant menus and food delivery apps live or die on photography. A broken dish photo—especially in the hero slot or a featured item card—reads as low-quality to the customer even when the food is excellent. Restaurant menu placeholder images bridge the gap between a SKU being added to the system and the actual photo arriving from the photography team.

The fallback.pics API generates warm-toned, correctly sized placeholder images from a URL with no upload required. Point your img onerror at the right endpoint and every menu item renders with a consistent visual regardless of photo status.

Why it matters

Missing dish photos increase abandonment on food delivery apps

Studies of food delivery UX consistently show that menu items without photos receive significantly fewer clicks. The pattern is intuitive: diners use photos to decide. A broken image icon or a collapsed layout slot is worse than a neutral placeholder because it signals neglect.

The problem is structural. New menu items go live before the photography workflow completes. Specials change daily. Seasonal items rotate. Even well-staffed operations have windows where photos are absent. A reliable restaurant menu placeholder image closes that window without blocking the menu publish.

The secondary issue is layout shift. When an img element has no src fallback, browsers collapse the element to zero height. That collapses the card, reflows the grid, and produces a cumulative layout shift score that Google measures. A dimensioned fallback prevents all of that.

Sizing guide

Standard dish photo dimensions for menus and delivery apps

Different contexts call for different aspect ratios. Full-width hero slots on a restaurant homepage typically use 1200x600 or 1440x600. Category header images on delivery apps land around 800x300. Individual item cards are most commonly 400x300 (4:3) or 600x400, though square 400x400 tiles are popular in grid-style menus.

For mobile-first apps, dish thumbnails appear at 160x120 in list views and 320x240 in card views. Always set explicit width and height attributes to reserve space before the placeholder or real image loads.

Implementation text
<!-- Full-width hero for restaurant homepage -->
<img
  src="https://fallback.pics/api/v1/800x600/F97316/FFFFFF?text=Featured+Dish"
  width="800"
  height="600"
  alt="Featured dish photo placeholder"
/>

<!-- Standard item card (4:3) -->
<img
  src="https://fallback.pics/api/v1/400x300/FB923C/FFFFFF?text=Menu+Item"
  width="400"
  height="300"
  alt="Menu item photo placeholder"
/>

<!-- Square tile for grid menus -->
<img
  src="https://fallback.pics/api/v1/square/400?text=Dish"
  width="400"
  height="400"
  alt="Dish image placeholder"
/>

onerror pattern

Wire the fallback into your img tag with onerror

The simplest integration is an inline onerror attribute that swaps in the placeholder URL when the real photo fails to load. This works in any HTML context—server-rendered templates, React JSX, Vue templates, or plain HTML files.

One important detail: set the onerror to clear itself before assigning the fallback src. If the fallback URL itself ever fails (unlikely for a CDN-hosted service, but worth guarding), without clearing onerror you get an infinite loop of error events trying to load the same broken URL.

Implementation text
<!-- Inline onerror with loop guard -->
<img
  src="{{ dish.photo_url }}"
  width="400"
  height="300"
  alt="{{ dish.name }}"
  onerror="this.onerror=null;this.src='https://fallback.pics/api/v1/400x300/F97316/FFFFFF?text=No+Photo'"
/>

<!-- React equivalent -->
<img
  src={dish.photoUrl}
  width={400}
  height={300}
  alt={dish.name}
  onError={(e) => {
    e.currentTarget.onerror = null;
    e.currentTarget.src =
      'https://fallback.pics/api/v1/400x300/F97316/FFFFFF?text=No+Photo';
  }}
/>

Category colors

Assign placeholder colors by food category

A flat gray placeholder for every missing dish photo is functional but uninspired. A small mapping table lets you assign warm oranges to mains, greens to salads, yellows to desserts, and blues to drinks. The color communicates category even before the real photo loads.

This approach also makes the 'missing photo' state feel intentional rather than accidental. Customers perceive it as a design choice, not a bug.

Implementation tsx
const CATEGORY_COLORS: Record<string, { bg: string; fg: string }> = {
  mains:    { bg: 'F97316', fg: 'FFFFFF' },
  salads:   { bg: '10B981', fg: 'FFFFFF' },
  desserts: { bg: 'FBBF24', fg: '1F2937' },
  drinks:   { bg: '3B82F6', fg: 'FFFFFF' },
  default:  { bg: '7C3AED', fg: 'FFFFFF' },
};

function dishFallbackUrl(category: string, name: string, w = 400, h = 300) {
  const { bg, fg } = CATEGORY_COLORS[category] ?? CATEGORY_COLORS.default;
  const text = encodeURIComponent(name.slice(0, 20));
  return `https://fallback.pics/api/v1/${w}x${h}/${bg}/${fg}?text=${text}`;
}

Delivery app pattern

Handle CDN images with unknown failure modes

Food delivery platforms source dish photos from restaurant operators, third-party photography services, and sometimes AI-generated assets. Each source has its own failure mode: the restaurant deletes the photo from their system, the CDN key expires, the image CDN returns a 404 for a renamed path. None of those failures are under your control.

A universal fallback at the component level means none of those upstream failures ever produce a broken image icon. Wrap the img in a component, centralize the fallback URL construction, and never rely on individual restaurant systems being reliable.

Implementation tsx
// DishPhoto.tsx
interface DishPhotoProps {
  src?: string;
  name: string;
  category: string;
  width?: number;
  height?: number;
}

export function DishPhoto({
  src,
  name,
  category,
  width = 400,
  height = 300,
}: DishPhotoProps) {
  const fallback = dishFallbackUrl(category, name, width, height);
  return (
    <img
      src={src ?? fallback}
      width={width}
      height={height}
      alt={name}
      loading="lazy"
      onError={(e) => {
        e.currentTarget.onerror = null;
        e.currentTarget.src = fallback;
      }}
    />
  );
}

Animated option

Use skeleton placeholders while photos load asynchronously

If your app fetches dish photo URLs from an API rather than embedding them in the initial HTML, there is a window where the component renders but the URL is not yet known. A skeleton placeholder is a better UX than a blank box during that window.

The animated skeleton route produces a shimmer-effect SVG at any dimension. Show it while the data fetch is in-flight, then swap in the real photo or the static fallback based on what the API returns.

Implementation text
<!-- Animated skeleton while data loads -->
<img
  src="https://fallback.pics/api/v1/animated/skeleton/400x300"
  width="400"
  height="300"
  alt="Loading dish photo"
/>

Internal links

More on image fallback patterns

The patterns in this post apply to any image-heavy ecommerce context. The fallback.pics documentation covers the full API surface including color parameters, text truncation, and format options.

Implementation text
https://fallback.pics/docs/
https://fallback.pics/placeholder-image-api/
https://fallback.pics/blog/food-menu-image-fallbacks/
https://fallback.pics/blog/cart-thumbnail-image-fallback/

Key takeaways

What to standardize before shipping

  • Missing dish photos cause measurable abandonment; a styled placeholder is always better than a broken icon.
  • Set explicit width and height on every img element to prevent layout shift regardless of photo load state.
  • Clear onerror before assigning the fallback src to prevent infinite error loops.
  • Map food categories to placeholder colors so missing photos still communicate visual category.
  • Use animated skeleton placeholders during async data fetches, then swap to static fallbacks or real photos.

Production fallback layer

Use fallback.pics anywhere an image URL is accepted.

Start with one deterministic URL and standardize fallback behavior across your design system.

Read the docs