> ## Documentation Index
> Fetch the complete documentation index at: https://docs.metabind.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Shader

> A GPU-rendered component for custom visual effects using fragment shaders.

export const PlatformStatuses = ({statuses}) => {
  const StatusBadge = ({status, label}) => {
    const styles = {
      green: {
        backgroundColor: '#dcfce7',
        color: '#166534'
      },
      orange: {
        backgroundColor: '#fed7aa',
        color: '#9a3412'
      },
      red: {
        backgroundColor: '#fecaca',
        color: '#991b1b'
      },
      gray: {
        backgroundColor: '#f3f4f6',
        color: '#4b5563'
      }
    };
    const baseStyle = {
      display: 'inline-flex',
      alignItems: 'center',
      padding: '0.125rem 0.625rem',
      borderRadius: '9999px',
      fontSize: '0.875rem',
      fontWeight: '500'
    };
    const colorStyle = styles[status] || styles.green;
    return <span style={{
      ...baseStyle,
      ...colorStyle
    }}>
        {label || status}
      </span>;
  };
  const STATUS_CONFIG = {
    supported: {
      label: "Supported",
      color: "green"
    },
    partial: {
      label: "Partial",
      color: "orange"
    },
    "not-implemented": {
      label: "Not Implemented",
      color: "gray"
    }
  };
  const renderCard = (platform, value) => {
    if (!value) return null;
    const {status, note} = typeof value === "string" ? {
      status: value
    } : value;
    const config = STATUS_CONFIG[status];
    if (!config) return null;
    const titleMap = {
      ios: "SwiftUI",
      android: "Jetpack Compose",
      web: "Web"
    };
    return <Card key={platform} title={titleMap[platform] || platform}>
          <StatusBadge status={config.color} label={config.label} />
          {note && <div style={{
      marginTop: '0.5rem',
      fontSize: '0.875rem',
      color: '#6b7280'
    }}>
              {note}
            </div>}
      </Card>;
  };
  if (statuses == null) {
    return null;
  }
  return <Columns cols="3">
      {Object.entries(statuses).map(([platform, value]) => renderCard(platform, value))}
    </Columns>;
};

export const ComposeJS = ({code, name, height}) => {
  let encodedCode = "";
  if (code) {
    try {
      encodedCode = btoa(code);
    } catch (e) {
      console.error("Failed to encode code", e);
    }
  }
  if (!encodedCode) {
    return null;
  }
  return <iframe src={`https://www.metabind.ai/embed?code=${encodeURIComponent(encodedCode)}&name=${encodeURIComponent(name ?? 'Example')}`} loading="lazy" style={{
    width: "100%",
    height: height || '350px',
    border: "1px solid #e5e7eb",
    borderRadius: "var(--rounded-2xl,1rem)",
    overflow: "hidden"
  }} title="ComposeJS Preview" />;
};

export const previewCode = `Shader({
    fragmentShader: "void main() { vec2 uv = gl_FragCoord.xy / resolution.xy; gl_FragColor = vec4(uv.x, uv.y, 0.8, 1.0); }"
})
    .frame({ width: 200, height: 200 })
`;

<ComposeJS code={previewCode} name={"Shader"} height="280px" />

```typescript theme={null}
Shader(props?: {
    fragmentShader?: string;
    uniforms?: Record<string, number | number[] | boolean>;
    timeEnabled?: boolean;
    mouseEnabled?: boolean;
    updateInterval?: number;
}): Component;
```

<ParamField path="props" type="object" optional>
  Configuration options for the shader.

  <Expandable title="properties">
    <ParamField path="fragmentShader" type="string" optional>
      Fragment shader source code in GLSL. The entry point is `void main()`, which writes the pixel color to `gl_FragColor`.
    </ParamField>

    <ParamField path="uniforms" type="Record<string, number | number[] | boolean>" optional>
      Custom uniform values passed to the shader. Keys become uniform names. Values can be numbers, arrays of numbers (for vectors), or booleans.
    </ParamField>

    <ParamField path="timeEnabled" type="boolean" optional>
      Whether to update the `time` uniform (seconds since the shader started) on each frame. Defaults to `true`.
    </ParamField>

    <ParamField path="mouseEnabled" type="boolean" optional>
      Whether to update the `mouse` uniform with the mouse position, normalized to 0–1 from the bottom-left corner. Defaults to `true`.
    </ParamField>

    <ParamField path="updateInterval" type="number" optional>
      Redraw rate in frames per second for animated shaders. When omitted, the shader redraws on every animation frame. `0` draws a single frame.
    </ParamField>
  </Expandable>
</ParamField>

<Note>
  The **web** renderer compiles `fragmentShader` as a plain GLSL fragment shader: write `void main()`, read `gl_FragCoord` and the `time`, `resolution` and `mouse` uniforms, and write to `gl_FragColor`. A Shadertoy-style `mainImage` body fails to compile with `Missing main()` and renders a blank canvas.
</Note>

## Support

<PlatformStatuses
  statuses={{
ios: "not-implemented",
android: "not-implemented",
web: { status: "supported" },
}}
/>

## Usage

### Solid color shader

```typescript theme={null}
Shader({
    fragmentShader: `void main() {
        gl_FragColor = vec4(0.2, 0.5, 1.0, 1.0);
    }`
})
    .frame({ width: 200, height: 200 })
```

### Gradient shader

```typescript theme={null}
Shader({
    fragmentShader: `void main() {
        vec2 uv = gl_FragCoord.xy / resolution.xy;
        gl_FragColor = vec4(uv.x, uv.y, 0.5, 1.0);
    }`
})
    .frame({ width: 300, height: 200 })
```

### Animated shader

```typescript theme={null}
Shader({
    fragmentShader: `void main() {
        vec2 uv = gl_FragCoord.xy / resolution.xy;
        float t = time * 0.5;
        vec3 col = 0.5 + 0.5 * cos(t + uv.xyx + vec3(0, 2, 4));
        gl_FragColor = vec4(col, 1.0);
    }`,
    timeEnabled: true
})
    .frame({ width: 300, height: 200 })
```

### With custom uniforms

```typescript theme={null}
Shader({
    fragmentShader: `uniform float speed;
uniform vec3 baseColor;

void main() {
    vec2 uv = gl_FragCoord.xy / resolution.xy;
    float wave = sin(uv.x * 10.0 + time * speed);
    gl_FragColor = vec4(baseColor * (0.5 + 0.5 * wave), 1.0);
}`,
    uniforms: {
        speed: 2.0,
        baseColor: [1.0, 0.5, 0.0]
    },
    timeEnabled: true
})
    .frame({ width: 300, height: 200 })
```

### Interactive shader

```typescript theme={null}
Shader({
    fragmentShader: `void main() {
        vec2 uv = gl_FragCoord.xy / resolution.xy;
        float d = distance(uv, mouse);
        float glow = 0.02 / d;
        gl_FragColor = vec4(glow * vec3(0.3, 0.6, 1.0), 1.0);
    }`,
    mouseEnabled: true
})
    .frame({ width: 300, height: 300 })
```

## Notes

* Shaders use WebGL. Built-in uniforms are `resolution` (canvas size in device pixels), `time` (seconds since start, unless `timeEnabled` is `false`), and `mouse` (normalized mouse position, unless `mouseEnabled` is `false`).
* The Shader component is web-only. On iOS and Android, it will not render.
* For best performance, keep shader complexity low and use `updateInterval` to throttle redraws when frame-perfect animation is not needed.

## See Also

* [Rectangle](/bindjs/components/shapes#rectangle) — simple filled rectangle
* [Path](/bindjs/components/shapes#path) — custom vector shapes


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.