---
title: Ring
description: Circular progress indicator with centered label.
icon: circle-notch
---

import { ComponentPreview } from '/snippets/component-preview.mdx'

Ring displays a value as a circular arc that fills clockwise from 12 o'clock. A centered label shows the value or any custom text. It shares the same `value`/`min`/`max`/`variant` API as Progress but takes up less horizontal space, making it ideal for dashboards and status panels.

## Basic Usage

<ComponentPreview json={{"view":{"type":"Ring","value":72.0,"min":0,"max":100,"label":"72%","variant":"default","size":"default","thickness":6}}} playground="ZnJvbSBwcmVmYWJfdWkuY29tcG9uZW50cyBpbXBvcnQgUmluZwoKUmluZyh2YWx1ZT03MiwgbGFiZWw9IjcyJSIpCg==">
<CodeGroup>
```python Python icon="python"
from prefab_ui.components import Ring

Ring(value=72, label="72%")
```
```json Protocol icon="brackets-curly"
{
  "view": {
    "type": "Ring",
    "value": 72.0,
    "min": 0,
    "max": 100,
    "label": "72%",
    "variant": "default",
    "size": "default",
    "thickness": 6
  }
}
```
</CodeGroup>
</ComponentPreview>

## Sizes

Three sizes control the ring diameter: `sm` (64px), `default` (96px), and `lg` (128px).

<ComponentPreview json={{"view":{"cssClass":"gap-6 items-end","type":"Row","children":[{"type":"Ring","value":60.0,"min":0,"max":100,"label":"sm","variant":"default","size":"sm","thickness":6},{"type":"Ring","value":60.0,"min":0,"max":100,"label":"default","variant":"default","size":"default","thickness":6},{"type":"Ring","value":60.0,"min":0,"max":100,"label":"lg","variant":"default","size":"lg","thickness":6}]}}} playground="ZnJvbSBwcmVmYWJfdWkuY29tcG9uZW50cyBpbXBvcnQgUmluZywgUm93Cgp3aXRoIFJvdyhjc3NfY2xhc3M9ImdhcC02IGl0ZW1zLWVuZCIpOgogICAgUmluZyh2YWx1ZT02MCwgc2l6ZT0ic20iLCBsYWJlbD0ic20iKQogICAgUmluZyh2YWx1ZT02MCwgc2l6ZT0iZGVmYXVsdCIsIGxhYmVsPSJkZWZhdWx0IikKICAgIFJpbmcodmFsdWU9NjAsIHNpemU9ImxnIiwgbGFiZWw9ImxnIikK">
<CodeGroup>
```python Python icon="python"
from prefab_ui.components import Ring, Row

with Row(css_class="gap-6 items-end"):
    Ring(value=60, size="sm", label="sm")
    Ring(value=60, size="default", label="default")
    Ring(value=60, size="lg", label="lg")
```
```json Protocol icon="brackets-curly"
{
  "view": {
    "cssClass": "gap-6 items-end",
    "type": "Row",
    "children": [
      {
        "type": "Ring",
        "value": 60.0,
        "min": 0,
        "max": 100,
        "label": "sm",
        "variant": "default",
        "size": "sm",
        "thickness": 6
      },
      {
        "type": "Ring",
        "value": 60.0,
        "min": 0,
        "max": 100,
        "label": "default",
        "variant": "default",
        "size": "default",
        "thickness": 6
      },
      {
        "type": "Ring",
        "value": 60.0,
        "min": 0,
        "max": 100,
        "label": "lg",
        "variant": "default",
        "size": "lg",
        "thickness": 6
      }
    ]
  }
}
```
</CodeGroup>
</ComponentPreview>

## Variants

Semantic variants color the filled arc to communicate meaning at a glance:

<ComponentPreview json={{"view":{"cssClass":"gap-4 flex-wrap","type":"Row","children":[{"type":"Ring","value":60.0,"min":0,"max":100,"label":"60%","variant":"default","size":"default","thickness":6},{"type":"Ring","value":100.0,"min":0,"max":100,"label":"100%","variant":"success","size":"default","thickness":6},{"type":"Ring","value":72.0,"min":0,"max":100,"label":"72%","variant":"warning","size":"default","thickness":6},{"type":"Ring","value":90.0,"min":0,"max":100,"label":"90%","variant":"destructive","size":"default","thickness":6},{"type":"Ring","value":45.0,"min":0,"max":100,"label":"45%","variant":"info","size":"default","thickness":6},{"type":"Ring","value":30.0,"min":0,"max":100,"label":"30%","variant":"muted","size":"default","thickness":6}]}}} playground="ZnJvbSBwcmVmYWJfdWkuY29tcG9uZW50cyBpbXBvcnQgUmluZywgUm93Cgp3aXRoIFJvdyhjc3NfY2xhc3M9ImdhcC00IGZsZXgtd3JhcCIpOgogICAgUmluZyh2YWx1ZT02MCwgbGFiZWw9IjYwJSIpCiAgICBSaW5nKHZhbHVlPTEwMCwgdmFyaWFudD0ic3VjY2VzcyIsIGxhYmVsPSIxMDAlIikKICAgIFJpbmcodmFsdWU9NzIsIHZhcmlhbnQ9Indhcm5pbmciLCBsYWJlbD0iNzIlIikKICAgIFJpbmcodmFsdWU9OTAsIHZhcmlhbnQ9ImRlc3RydWN0aXZlIiwgbGFiZWw9IjkwJSIpCiAgICBSaW5nKHZhbHVlPTQ1LCB2YXJpYW50PSJpbmZvIiwgbGFiZWw9IjQ1JSIpCiAgICBSaW5nKHZhbHVlPTMwLCB2YXJpYW50PSJtdXRlZCIsIGxhYmVsPSIzMCUiKQo=">
<CodeGroup>
```python Python icon="python"
from prefab_ui.components import Ring, Row

with Row(css_class="gap-4 flex-wrap"):
    Ring(value=60, label="60%")
    Ring(value=100, variant="success", label="100%")
    Ring(value=72, variant="warning", label="72%")
    Ring(value=90, variant="destructive", label="90%")
    Ring(value=45, variant="info", label="45%")
    Ring(value=30, variant="muted", label="30%")
```
```json Protocol icon="brackets-curly"
{
  "view": {
    "cssClass": "gap-4 flex-wrap",
    "type": "Row",
    "children": [
      {
        "type": "Ring",
        "value": 60.0,
        "min": 0,
        "max": 100,
        "label": "60%",
        "variant": "default",
        "size": "default",
        "thickness": 6
      },
      {
        "type": "Ring",
        "value": 100.0,
        "min": 0,
        "max": 100,
        "label": "100%",
        "variant": "success",
        "size": "default",
        "thickness": 6
      },
      {
        "type": "Ring",
        "value": 72.0,
        "min": 0,
        "max": 100,
        "label": "72%",
        "variant": "warning",
        "size": "default",
        "thickness": 6
      },
      {
        "type": "Ring",
        "value": 90.0,
        "min": 0,
        "max": 100,
        "label": "90%",
        "variant": "destructive",
        "size": "default",
        "thickness": 6
      },
      {
        "type": "Ring",
        "value": 45.0,
        "min": 0,
        "max": 100,
        "label": "45%",
        "variant": "info",
        "size": "default",
        "thickness": 6
      },
      {
        "type": "Ring",
        "value": 30.0,
        "min": 0,
        "max": 100,
        "label": "30%",
        "variant": "muted",
        "size": "default",
        "thickness": 6
      }
    ]
  }
}
```
</CodeGroup>
</ComponentPreview>

## Thickness

The `thickness` prop controls the stroke width of the ring arc. The default is 6.

<ComponentPreview json={{"view":{"cssClass":"gap-6 items-end","type":"Row","children":[{"type":"Ring","value":75.0,"min":0,"max":100,"label":"3","variant":"default","size":"default","thickness":3.0},{"type":"Ring","value":75.0,"min":0,"max":100,"label":"6","variant":"default","size":"default","thickness":6.0},{"type":"Ring","value":75.0,"min":0,"max":100,"label":"12","variant":"default","size":"default","thickness":12.0}]}}} playground="ZnJvbSBwcmVmYWJfdWkuY29tcG9uZW50cyBpbXBvcnQgUmluZywgUm93Cgp3aXRoIFJvdyhjc3NfY2xhc3M9ImdhcC02IGl0ZW1zLWVuZCIpOgogICAgUmluZyh2YWx1ZT03NSwgdGhpY2tuZXNzPTMsIGxhYmVsPSIzIikKICAgIFJpbmcodmFsdWU9NzUsIHRoaWNrbmVzcz02LCBsYWJlbD0iNiIpCiAgICBSaW5nKHZhbHVlPTc1LCB0aGlja25lc3M9MTIsIGxhYmVsPSIxMiIpCg==">
<CodeGroup>
```python Python icon="python"
from prefab_ui.components import Ring, Row

with Row(css_class="gap-6 items-end"):
    Ring(value=75, thickness=3, label="3")
    Ring(value=75, thickness=6, label="6")
    Ring(value=75, thickness=12, label="12")
```
```json Protocol icon="brackets-curly"
{
  "view": {
    "cssClass": "gap-6 items-end",
    "type": "Row",
    "children": [
      {
        "type": "Ring",
        "value": 75.0,
        "min": 0,
        "max": 100,
        "label": "3",
        "variant": "default",
        "size": "default",
        "thickness": 3.0
      },
      {
        "type": "Ring",
        "value": 75.0,
        "min": 0,
        "max": 100,
        "label": "6",
        "variant": "default",
        "size": "default",
        "thickness": 6.0
      },
      {
        "type": "Ring",
        "value": 75.0,
        "min": 0,
        "max": 100,
        "label": "12",
        "variant": "default",
        "size": "default",
        "thickness": 12.0
      }
    ]
  }
}
```
</CodeGroup>
</ComponentPreview>

## Indicator Class

`indicator_class` applies Tailwind classes directly to the filled arc, just like Progress's `indicator_class` targets the bar. The Ring wrapper acts as a `group`, so `group-hover:` classes let you trigger effects when hovering anywhere on the ring.

Because the Ring arc is an SVG stroke, use `stroke-*` classes (not `bg-*`) to override colors.

<ComponentPreview json={{"view":{"cssClass":"gap-6","type":"Row","children":[{"type":"Ring","value":72.0,"min":0,"max":100,"label":"72%","variant":"info","size":"default","thickness":6,"indicatorClass":"group-hover:drop-shadow-[0_0_20px_rgba(59,130,246,0.8)]"},{"type":"Ring","value":90.0,"min":0,"max":100,"label":"90%","variant":"default","size":"default","thickness":6,"indicatorClass":"stroke-emerald-500 group-hover:drop-shadow-[0_0_20px_rgba(16,185,129,0.8)]"}]}}} playground=" ">
<CodeGroup>
```python Python icon="python"
from prefab_ui.components import Ring, Row

with Row(css_class="gap-6"):
    Ring(
        value=72, label="72%", variant="info",
        indicator_class=(
            "group-hover:drop-shadow-[0_0_20px_rgba(59,130,246,0.8)]"
        ),
    )
    Ring(
        value=90, label="90%",
        indicator_class=(
            "stroke-emerald-500"
            " group-hover:drop-shadow-[0_0_20px_rgba(16,185,129,0.8)]"
        ),
    )
```
```json Protocol icon="brackets-curly"
{
  "view": {
    "cssClass": "gap-6",
    "type": "Row",
    "children": [
      {
        "type": "Ring",
        "value": 72.0,
        "min": 0,
        "max": 100,
        "label": "72%",
        "variant": "info",
        "size": "default",
        "thickness": 6,
        "indicatorClass": "group-hover:drop-shadow-[0_0_20px_rgba(59,130,246,0.8)]"
      },
      {
        "type": "Ring",
        "value": 90.0,
        "min": 0,
        "max": 100,
        "label": "90%",
        "variant": "default",
        "size": "default",
        "thickness": 6,
        "indicatorClass": "stroke-emerald-500 group-hover:drop-shadow-[0_0_20px_rgba(16,185,129,0.8)]"
      }
    ]
  }
}
```
</CodeGroup>
</ComponentPreview>

## Custom Range

Set `min` and `max` to define a range other than 0–100. The renderer normalizes the value to a percentage automatically.

<ComponentPreview json={{"view":{"cssClass":"gap-6","type":"Row","children":[{"type":"Ring","value":72.0,"min":60.0,"max":100.0,"label":"72\u00b0F","variant":"warning","size":"default","thickness":6},{"type":"Ring","value":3.0,"min":0,"max":5.0,"label":"3/5","variant":"info","size":"default","thickness":6}]}}} playground="ZnJvbSBwcmVmYWJfdWkuY29tcG9uZW50cyBpbXBvcnQgUmluZywgUm93Cgp3aXRoIFJvdyhjc3NfY2xhc3M9ImdhcC02Iik6CiAgICBSaW5nKHZhbHVlPTcyLCBtaW49NjAsIG1heD0xMDAsIHZhcmlhbnQ9Indhcm5pbmciLCBsYWJlbD0iNzJcdTAwYjBGIikKICAgIFJpbmcodmFsdWU9MywgbWF4PTUsIHZhcmlhbnQ9ImluZm8iLCBsYWJlbD0iMy81IikK">
<CodeGroup>
```python Python icon="python"
from prefab_ui.components import Ring, Row

with Row(css_class="gap-6"):
    Ring(value=72, min=60, max=100, variant="warning", label="72\u00b0F")
    Ring(value=3, max=5, variant="info", label="3/5")
```
```json Protocol icon="brackets-curly"
{
  "view": {
    "cssClass": "gap-6",
    "type": "Row",
    "children": [
      {
        "type": "Ring",
        "value": 72.0,
        "min": 60.0,
        "max": 100.0,
        "label": "72\u00b0F",
        "variant": "warning",
        "size": "default",
        "thickness": 6
      },
      {
        "type": "Ring",
        "value": 3.0,
        "min": 0,
        "max": 5.0,
        "label": "3/5",
        "variant": "info",
        "size": "default",
        "thickness": 6
      }
    ]
  }
}
```
</CodeGroup>
</ComponentPreview>

## Custom Center Content

Use Ring as a container to place arbitrary components in the center. When children are present, they replace the `label` text.

<ComponentPreview json={{"view":{"cssClass":"gap-6","type":"Row","children":[{"type":"Ring","value":75.0,"min":0,"max":100,"variant":"success","size":"lg","thickness":8.0,"children":[{"cssClass":"text-3xl font-bold","content":"75%","type":"Text"}]},{"type":"Ring","value":3.0,"min":0,"max":5.0,"variant":"info","size":"lg","thickness":8.0,"children":[{"cssClass":"text-center","type":"Column","children":[{"cssClass":"text-2xl font-bold","content":"3/5","type":"Text"},{"cssClass":"text-xs text-muted-foreground","content":"steps","type":"Text"}]}]}]}}} playground=" ">
<CodeGroup>
```python Python icon="python"
from prefab_ui.components import Column, Ring, Row, Text

with Row(css_class="gap-6"):
    with Ring(value=75, variant="success", size="lg", thickness=8):
        Text("75%", css_class="text-3xl font-bold")
    with Ring(value=3, max=5, variant="info", size="lg", thickness=8):
        with Column(css_class="text-center"):
            Text("3/5", css_class="text-2xl font-bold")
            Text("steps", css_class="text-xs text-muted-foreground")
```
```json Protocol icon="brackets-curly"
{
  "view": {
    "cssClass": "gap-6",
    "type": "Row",
    "children": [
      {
        "type": "Ring",
        "value": 75.0,
        "min": 0,
        "max": 100,
        "variant": "success",
        "size": "lg",
        "thickness": 8.0,
        "children": [{"cssClass": "text-3xl font-bold", "content": "75%", "type": "Text"}]
      },
      {
        "type": "Ring",
        "value": 3.0,
        "min": 0,
        "max": 5.0,
        "variant": "info",
        "size": "lg",
        "thickness": 8.0,
        "children": [
          {
            "cssClass": "text-center",
            "type": "Column",
            "children": [
              {"cssClass": "text-2xl font-bold", "content": "3/5", "type": "Text"},
              {
                "cssClass": "text-xs text-muted-foreground",
                "content": "steps",
                "type": "Text"
              }
            ]
          }
        ]
      }
    ]
  }
}
```
</CodeGroup>
</ComponentPreview>

## API Reference

<Card icon="code" title="Ring Parameters">
<ParamField body="value" type="float | str" default="0">
  Current value. Can be a template expression for dynamic binding.
</ParamField>

<ParamField body="min" type="float" default="0">
  Minimum value. The renderer normalizes `(value - min) / (max - min)` to a 0–100 percentage.
</ParamField>

<ParamField body="max" type="float" default="100">
  Maximum value.
</ParamField>

<ParamField body="label" type="str | None" default="None">
  Text displayed in the center of the ring. Supports template expressions. Ignored when children are present.
</ParamField>

<ParamField body="children" type="list[Component]" default="[]">
  Arbitrary components rendered in the center of the ring, replacing the label.
</ParamField>

<ParamField body="variant" type="str" default="default">
  Visual variant: `"default"`, `"success"`, `"warning"`, `"destructive"`, `"info"`, `"muted"`.
</ParamField>

<ParamField body="size" type="str" default="default">
  Ring diameter: `"sm"` (64px), `"default"` (96px), `"lg"` (128px).
</ParamField>

<ParamField body="thickness" type="float" default="6">
  Stroke width of the ring arc in pixels.
</ParamField>

<ParamField body="indicator_class" type="str | None" default="None">
  Tailwind classes applied to the filled arc. The Ring wrapper is a `group`, so `group-hover:` classes trigger on hover anywhere on the ring.
</ParamField>

<ParamField body="css_class" type="str | None" default="None">
  Tailwind classes for the outer container.
</ParamField>
</Card>

## Protocol Reference

```json Ring
{
  "type": "Ring",
  "children?": "[Component]",
  "let?": "object",
  "value?": "number | string",
  "min?": 0,
  "max?": 100,
  "label?": "string",
  "variant?": "default | success | warning | destructive | info | muted",
  "size?": "sm | default | lg",
  "thickness?": 6,
  "indicatorClass?": "string",
  "cssClass?": "string"
}
```

For the complete protocol schema, see [Ring](/protocol/ring).
