Browser Support

Firefox Behind a Flag and the Road to Universal Support

Firefox 555 implements WebGPU through wgpu (wgpu) and ships it one platform at a time, which is why caniuse.com 3,038 scores it as partial:

WebGPU in Firefox by platform (gpuweb Implementation Status wiki)
Firefox platform Status in September 2026
Windows On by default since 141
macOS on Apple Silicon On by default since 145
macOS on Intel, Linux Nightly only; Linux expected to ship in 2026
Android Behind a flag in Beta and Nightly

On a flagged platform, a developer can set dom.webgpu.enabled to true in about:config (Android also needs gfx.webgpu.ignore-blocklist), but visitors will not, so a Linux Firefox user still lands on your fallback. Safari 10 also needs macOS 26 on the desktop, and Chrome 1 on Linux depends on the GPU. Until WebGPU is Baseline, follow Migration Paths: keep a WebGL 2 path, or use a library that switches for you (Three.js and Babylon.js).

To exercise the no-adapter branch of Browser Support in 2026's check, launch Chrome with --disable-gpu: navigator.gpu stays, but requestAdapter() returns null (checked in Chrome 154 here).

WebGPU status in Firefox by platform, plus a live check of this browserHTMLLive
<!doctype html>
<style>
  body { margin: 0; background: #f7f4ee; font: 14px system-ui, sans-serif; }
  .stage { position: relative; width: 100%; max-width: 600px; }
  .stage canvas { display: block; width: 100%; }
  .stage canvas + canvas { position: absolute; inset: 0; pointer-events: none; }
</style>
<div class="stage">
  <canvas id="view" width="600" height="340"></canvas>
  <canvas id="labels" width="600" height="340"></canvas>
</div>
<script>
const canvas = document.getElementById('view');
const ink = document.getElementById('labels').getContext('2d');

function showMessage(text) {                     // 2D fallback when WebGPU is missing
  const ctx = canvas.getContext('2d');
  ctx.fillStyle = '#fbeaea'; ctx.fillRect(0, 0, canvas.width, canvas.height);
  ctx.fillStyle = '#8a2b2b'; ctx.font = '18px system-ui, sans-serif'; ctx.textAlign = 'center';
  ctx.fillText(text, canvas.width / 2, canvas.height / 2);
}
function label(text, x, y, size = 12, color = '#2b2b2b', align = 'left', weight = '') {
  ink.font = `${weight} ${size}px system-ui, sans-serif`; ink.fillStyle = color; ink.textAlign = align;
  ink.fillText(text, x, y);
}

const ON = [0.16, 0.56, 0.30], NIGHTLY = [0.88, 0.60, 0.10], FLAG = [0.75, 0.30, 0.25];
const platforms = [                                // Firefox, September 2026
  ['Windows', ON, 'On by default since Firefox 141'],
  ['macOS, Apple Silicon', ON, 'On by default since Firefox 145'],
  ['macOS, Intel', NIGHTLY, 'Nightly only'],
  ['Linux', NIGHTLY, 'Nightly only; expected to ship in 2026'],
  ['Android', FLAG, 'Behind a flag in Beta and Nightly']];

async function main() {
  // The live check runs first, so the fallback path shows the same verdict.
  const hasApi = 'gpu' in navigator;
  const adapter = hasApi ? await navigator.gpu.requestAdapter() : null;
  if (!adapter) return showMessage(hasApi ? 'navigator.gpu exists, but requestAdapter() returned null'
                                          : 'WebGPU is not available in this browser');
  const device = await adapter.requestDevice();
  const context = canvas.getContext('webgpu');
  const format = navigator.gpu.getPreferredCanvasFormat();
  context.configure({ device, format });

  const boxes = [];                                  // x, y, w, h, r, g, b, corner radius
  label('WebGPU in Firefox, by platform', 20, 28, 15, '#222', 'left', 'bold');
  platforms.forEach(([name, color, status], i) => {
    const y = 44 + i * 44;
    boxes.push([14, y, 572, 38, 1, 1, 1, 8], [24, y + 9, 20, 20, ...color, 10]);
    label(name, 56, y + 24, 14, '#222');
    label(status, 250, y + 24, 13, '#444');
  });
  // Live result for the browser showing this page
  boxes.push([14, 272, 572, 52, 0.86, 0.93, 0.87, 10], [24, 288, 20, 20, ...ON, 10]);
  label('This browser: navigator.gpu present, requestAdapter() returned an adapter', 56, 294, 13, '#174d2b');
  label(`vendor "${adapter.info.vendor || 'n/a'}", architecture "${adapter.info.architecture || 'n/a'}"`, 56, 312, 12, '#35604a');

  const module = device.createShaderModule({ code: `
    struct Box { rect: vec4f, style: vec4f }          // style: r, g, b, corner radius
    @group(0) @binding(0) var<storage> boxes: array<Box>;
    struct Out { @builtin(position) pos: vec4f, @location(0) local: vec2f,
                 @location(1) @interpolate(flat) i: u32 }
    @vertex fn vs(@builtin(vertex_index) v: u32, @builtin(instance_index) i: u32) -> Out {
      let corner = vec2f(f32(v & 1), f32(v >> 1));   // triangle-strip corners
      let r = boxes[i].rect;
      let px = r.xy + corner * r.zw;
      return Out(vec4f(px.x / 300 - 1, 1 - px.y / 170, 0, 1), corner * r.zw, i);
    }
    @fragment fn fs(in: Out) -> @location(0) vec4f {
      let b = boxes[in.i];
      let half = b.rect.zw / 2;
      let q = abs(in.local - half) - half + b.style.w; // signed distance to a rounded box
      let d = length(max(q, vec2f(0))) + min(max(q.x, q.y), 0) - b.style.w;
      let a = clamp(0.5 - d, 0, 1);
      return vec4f(b.style.rgb * a, a);               // premultiplied alpha
    }` });
  const blend = { srcFactor: 'one', dstFactor: 'one-minus-src-alpha' };
  const pipeline = device.createRenderPipeline({ layout: 'auto',
    vertex: { module }, primitive: { topology: 'triangle-strip' },
    fragment: { module, targets: [{ format, blend: { color: blend, alpha: blend } }] } });
  const data = new Float32Array(boxes.flat());
  const buffer = device.createBuffer({ size: data.byteLength, usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST });
  device.queue.writeBuffer(buffer, 0, data);
  const group = device.createBindGroup({ layout: pipeline.getBindGroupLayout(0), entries: [{ binding: 0, resource: { buffer } }] });

  const encoder = device.createCommandEncoder();
  const pass = encoder.beginRenderPass({ colorAttachments: [{ view: context.getCurrentTexture().createView(),
    clearValue: [0.93, 0.91, 0.87, 1], loadOp: 'clear', storeOp: 'store' }] });
  pass.setPipeline(pipeline);
  pass.setBindGroup(0, group);
  pass.draw(4, boxes.length);
  pass.end();
  device.queue.submit([encoder.finish()]);
}
main();
</script>