> ## 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.

# Shapes

> Geometric primitives — rectangles, circles, capsules, and custom paths

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

Shapes are vector primitives that fill their frame. `Rectangle` and `RoundedRectangle` are sharp- and rounded-corner rectangles; `Capsule` is a rounded rectangle with maximum corner radius. `Circle` and `Ellipse` are radial primitives. `Path` builds an arbitrary vector shape from drawing commands.

Every shape supports `.fill(style)` to paint its interior and `.stroke(style)` to draw its outline. Shapes also work as `.clipShape()`, `.background()`, or `.overlay()` arguments for other components.

## Rectangle

A rectangle shape that fills its frame.

```typescript theme={null}
Rectangle(): Shape;
```

Rectangle takes no parameters. It creates a rectangle that fills its entire frame.

<ParamField path=".fill(style)" type="(style: Style) => Shape">
  Fills the rectangle interior with a color, gradient, or other style.
</ParamField>

<ParamField path=".stroke(style)" type="(style: Style | StrokeOptions) => Shape">
  Draws the rectangle outline. Pass a Style for a 1-point stroke, or a StrokeOptions object (`{ style: Style, lineWidth?: number }`) for custom width.
</ParamField>

<PlatformStatuses
  statuses={{
ios: { status: "supported" },
android: { status: "partial", note: ".stroke() is not supported" },
web: { status: "supported" },
}}
/>

**Basic rectangle**

```typescript theme={null}
Rectangle()
    .fill(Color("blue"))
    .frame({ width: 200, height: 100 })
```

**Stroked rectangle**

```typescript theme={null}
Rectangle()
    .stroke(Color("gray"), 1)
    .frame({ height: 1 })
```

**As a background**

```typescript theme={null}
Text("Hello")
    .padding(16)
    .background(
        Rectangle().fill(Color("yellow").opacity(0.3))
    )
```

**Color swatch**

```typescript theme={null}
HStack({ spacing: 4 }, [
    ForEach(["red", "orange", "yellow", "green", "blue"], (color) =>
        Rectangle()
            .fill(Color(color))
            .frame({ width: 32, height: 32 })
    )
])
```

<Note>
  For rounded corners, use [RoundedRectangle](#roundedrectangle) or the `.cornerRadius()` modifier.
</Note>

## RoundedRectangle

A rectangle with rounded corners.

```typescript theme={null}
RoundedRectangle(props?: { cornerRadius?: number }): Shape;
```

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

  <Expandable title="properties">
    <ParamField path="cornerRadius" type="number" optional>
      The radius of the rounded corners in points. Defaults to `0` if omitted (equivalent to [Rectangle](#rectangle)).
    </ParamField>
  </Expandable>
</ParamField>

<ParamField path=".fill(style)" type="(style: Style) => Shape">
  Fills the rounded rectangle interior with a color, gradient, or other style.
</ParamField>

<ParamField path=".stroke(style)" type="(style: Style | StrokeOptions) => Shape">
  Draws the rounded rectangle outline. Pass a Style for a 1-point stroke, or a StrokeOptions object (`{ style: Style, lineWidth?: number }`) for custom width.
</ParamField>

<PlatformStatuses
  statuses={{
ios: { status: "supported" },
android: { status: "partial", note: ".stroke() is not supported" },
web: { status: "supported" },
}}
/>

**Basic rounded rectangle**

```typescript theme={null}
RoundedRectangle({ cornerRadius: 12 })
    .fill(Color("blue"))
    .frame({ width: 200, height: 100 })
```

**Stroked rounded rectangle**

```typescript theme={null}
RoundedRectangle({ cornerRadius: 8 })
    .stroke(Color("gray"), 2)
    .frame({ width: 160, height: 80 })
```

**Card background**

```typescript theme={null}
VStack({ spacing: 12 }, [
    Text("Card Title").font("headline"),
    Text("Card description goes here.")
])
    .padding(16)
    .background(
        RoundedRectangle({ cornerRadius: 16 })
            .fill(Color("secondarySystemBackground"))
    )
```

**With gradient fill**

```typescript theme={null}
RoundedRectangle({ cornerRadius: 20 })
    .fill(LinearGradient({
        colors: [Color("purple"), Color("blue")]
    }))
    .frame({ width: 240, height: 120 })
```

**As a clip shape**

```typescript theme={null}
Image({ url: "photo.jpg" })
    .frame({ width: 200, height: 150 })
    .clipShape(RoundedRectangle({ cornerRadius: 12 }))
```

<Note>
  For fully rounded ends (pill shape), use [Capsule](#capsule) instead of computing the corner radius manually. RoundedRectangle can also be used with `.clipShape()` and `.contentShape()` modifiers.
</Note>

## Circle

A circle shape centered in its frame.

```typescript theme={null}
Circle(): Shape;
```

Circle takes no parameters. It creates a circle that fills the smaller dimension of its frame.

<ParamField path=".fill(style)" type="(style: Style) => Shape">
  Fills the circle interior with a color, gradient, or other style.
</ParamField>

<ParamField path=".stroke(style)" type="(style: Style | StrokeOptions) => Shape">
  Draws the circle outline. Pass a Style for a 1-point stroke, or a StrokeOptions object (`{ style: Style, lineWidth?: number }`) for custom width.
</ParamField>

<PlatformStatuses
  statuses={{
ios: { status: "supported" },
android: { status: "partial", note: ".stroke() is not supported" },
web: { status: "supported" },
}}
/>

**Basic circle**

```typescript theme={null}
Circle()
    .fill(Color("blue"))
    .frame({ width: 50, height: 50 })
```

**Stroked circle**

```typescript theme={null}
Circle()
    .stroke(Color("red"), 2)
```

**With gradient fill**

```typescript theme={null}
Circle()
    .fill(LinearGradient({
        colors: [Color("purple"), Color("blue")]
    }))
    .frame({ width: 100, height: 100 })
```

**Avatar placeholder**

```typescript theme={null}
ZStack([
    Circle()
        .fill(Color("gray").opacity(0.3))
        .frame({ width: 64, height: 64 }),
    Text("JD")
        .font("headline")
        .foregroundStyle(Color("white"))
])
```

**As a clip shape**

```typescript theme={null}
Image({ url: "avatar.jpg" })
    .frame({ width: 48, height: 48 })
    .clipShape(Circle())
```

## Ellipse

An ellipse shape that fills its frame.

```typescript theme={null}
Ellipse(): Shape;
```

Ellipse takes no parameters. It creates an ellipse that fills the entire frame, stretching to fit both dimensions.

<ParamField path=".fill(style)" type="(style: Style) => Shape">
  Fills the ellipse interior with a color, gradient, or other style.
</ParamField>

<ParamField path=".stroke(style)" type="(style: Style | StrokeOptions) => Shape">
  Draws the ellipse outline. Pass a Style for a 1-point stroke, or a StrokeOptions object (`{ style: Style, lineWidth?: number }`) for custom width.
</ParamField>

<PlatformStatuses
  statuses={{
ios: { status: "supported" },
android: { status: "partial", note: ".stroke() is not supported" },
web: { status: "supported" },
}}
/>

**Basic ellipse**

```typescript theme={null}
Ellipse()
    .fill(Color("orange"))
    .frame({ width: 200, height: 100 })
```

**Stroked ellipse**

```typescript theme={null}
Ellipse()
    .stroke(Color("blue"), 3)
    .frame({ width: 150, height: 80 })
```

**With gradient fill**

```typescript theme={null}
Ellipse()
    .fill(LinearGradient({
        colors: [Color("green"), Color("teal")]
    }))
    .frame({ width: 200, height: 120 })
```

<Note>
  When the frame is square, Ellipse renders identically to [Circle](#circle).
</Note>

## Capsule

A capsule shape — a rounded rectangle with maximum corner radius.

```typescript theme={null}
Capsule(): Shape;
```

Capsule takes no parameters. It creates a rectangle with fully rounded ends (corner radius equals half the shorter dimension).

<ParamField path=".fill(style)" type="(style: Style) => Shape">
  Fills the capsule interior with a color, gradient, or other style.
</ParamField>

<ParamField path=".stroke(style)" type="(style: Style | StrokeOptions) => Shape">
  Draws the capsule outline. Pass a Style for a 1-point stroke, or a StrokeOptions object (`{ style: Style, lineWidth?: number }`) for custom width.
</ParamField>

<PlatformStatuses
  statuses={{
ios: { status: "supported" },
android: { status: "partial", note: ".stroke() is not supported" },
web: { status: "supported" },
}}
/>

**Basic capsule**

```typescript theme={null}
Capsule()
    .fill(Color("blue"))
    .frame({ width: 200, height: 44 })
```

**Stroked capsule**

```typescript theme={null}
Capsule()
    .stroke(Color("gray"), 2)
    .frame({ width: 180, height: 40 })
```

**Pill-shaped button background**

```typescript theme={null}
Text("Get Started")
    .foregroundStyle(Color("white"))
    .padding({ horizontal: 24, vertical: 12 })
    .background(
        Capsule().fill(Color("blue"))
    )
```

**Tag or badge**

```typescript theme={null}
HStack({ spacing: 4 }, [
    Text("NEW")
        .font("caption2")
        .bold()
        .foregroundStyle(Color("white"))
        .padding({ horizontal: 8, vertical: 4 })
        .background(Capsule().fill(Color("red")))
])
```

<Note>
  When the frame is square, Capsule renders as a circle. Use [Circle](#circle) directly if a circle is always intended. For a rounded rectangle with a specific corner radius, use [RoundedRectangle](#roundedrectangle) instead.
</Note>

## Path

Creates a custom vector shape from path drawing commands.

```typescript theme={null}
Path(builder: (path: PathBuilder) => void): Shape;
```

<ParamField path="builder" type="(path: PathBuilder) => void" required>
  A function that receives a PathBuilder and uses its drawing commands to construct the shape.
</ParamField>

The `path` parameter provides the following drawing commands:

<ParamField path="path.move(x, y)" type="(x: number, y: number) => void">
  Begins a new subpath at the given point.
</ParamField>

<ParamField path="path.line(x, y)" type="(x: number, y: number) => void">
  Adds a straight line from the current point to the given point.
</ParamField>

<ParamField path="path.quadCurve(x, y, controlX, controlY)" type="(x: number, y: number, controlX: number, controlY: number) => void">
  Adds a quadratic Bezier curve to the given point with one control point.
</ParamField>

<ParamField path="path.curve(x, y, control1X, control1Y, control2X, control2Y)" type="(x: number, y: number, control1X: number, control1Y: number, control2X: number, control2Y: number) => void">
  Adds a cubic Bezier curve to the given point with two control points.
</ParamField>

<ParamField path="path.arc(props)" type="(props: { centerX: number; centerY: number; radius: number; startAngle: number; endAngle: number; clockwise?: boolean }) => void">
  Adds an arc. Angles are specified in degrees.
</ParamField>

<ParamField path="path.addRect(x, y, width, height)" type="(x: number, y: number, width: number, height: number) => void">
  Adds a rectangle subpath.
</ParamField>

<ParamField path="path.addRoundedRect(props)" type="(props: { x: number; y: number; width: number; height: number; cornerWidth: number; cornerHeight: number }) => void">
  Adds a rounded rectangle subpath.
</ParamField>

<ParamField path="path.addEllipse(x, y, width, height)" type="(x: number, y: number, width: number, height: number) => void">
  Adds an ellipse subpath inscribed in the given rectangle.
</ParamField>

<ParamField path="path.addLines(points)" type="(points: [number, number][]) => void">
  Adds a polyline from an array of `[x, y]` coordinate pairs.
</ParamField>

<ParamField path="path.close()" type="() => void">
  Closes the current subpath by drawing a line back to the starting point.
</ParamField>

Since Path returns a Shape, it supports the standard shape methods:

<ParamField path=".fill(style)" type="(style: Style) => Shape">
  Fills the path interior with a color, gradient, or other style.
</ParamField>

<ParamField path=".stroke(style)" type="(style: Style | StrokeOptions) => Shape">
  Draws the path outline. Pass a Style for a 1-point stroke, or a StrokeOptions object (`{ style: Style, lineWidth?: number }`) for custom width.
</ParamField>

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

**Triangle**

```typescript theme={null}
Path((path) => {
    path.move(50, 0)
    path.line(100, 100)
    path.line(0, 100)
    path.close()
})
    .fill(Color("blue"))
    .frame({ width: 100, height: 100 })
```

**Star shape**

```typescript theme={null}
Path((path) => {
    path.move(50, 0)
    path.line(61, 36)
    path.line(100, 36)
    path.line(68, 58)
    path.line(79, 95)
    path.line(50, 72)
    path.line(21, 95)
    path.line(32, 58)
    path.line(0, 36)
    path.line(39, 36)
    path.close()
})
    .fill(Color("yellow"))
    .frame({ width: 100, height: 100 })
```

**Curved line**

```typescript theme={null}
Path((path) => {
    path.move(0, 50)
    path.quadCurve(100, 50, 50, 0)
})
    .stroke(Color("red"), 3)
    .frame({ width: 100, height: 60 })
```

**Arc**

```typescript theme={null}
Path((path) => {
    path.arc({
        centerX: 50,
        centerY: 50,
        radius: 40,
        startAngle: 0,
        endAngle: 270
    })
})
    .stroke(Color("blue"), 4)
    .frame({ width: 100, height: 100 })
```

**Polyline chart**

```typescript theme={null}
Path((path) => {
    path.addLines([
        [0, 80], [20, 60], [40, 70],
        [60, 30], [80, 50], [100, 10]
    ])
})
    .stroke(Color("green"), 2)
    .frame({ width: 200, height: 100 })
```

<Note>
  Coordinates are relative to the Path's own coordinate space — use `.frame()` to set the overall size. Path returns a Shape, so it can be used anywhere shapes are accepted, including `.clipShape()`, `.background()`, and `.overlay()`. Multiple subpaths can be created by calling `move()` multiple times within a single builder.
</Note>

## See also

* [LinearGradient](/bindjs/styles/LinearGradient) — gradient fills for shapes
* [Color](/bindjs/styles/Color) — solid color fills and strokes
* [Image](/bindjs/components/Image) — bitmap content to clip with shapes
* [ZStack](/bindjs/components/ZStack) — layer shapes behind or in front of content
