diff --git a/apps/www/src/content/docs/components/meter/demo.ts b/apps/www/src/content/docs/components/meter/demo.ts
new file mode 100644
index 00000000..fe373259
--- /dev/null
+++ b/apps/www/src/content/docs/components/meter/demo.ts
@@ -0,0 +1,140 @@
+'use client';
+
+import { getPropsString } from '@/lib/utils';
+
+export const getCode = (props: any) => {
+ return ``;
+};
+
+export const playground = {
+ type: 'playground',
+ controls: {
+ value: { type: 'number', initialValue: 40, min: 0, max: 100 },
+ variant: {
+ type: 'select',
+ initialValue: 'linear',
+ options: ['linear', 'circular']
+ },
+ min: { type: 'number', defaultValue: 0, min: 0, max: 99 },
+ max: { type: 'number', defaultValue: 100, min: 1, max: 100 }
+ },
+ getCode
+};
+
+export const directUsageDemo = {
+ type: 'code',
+ code: `
+
+
+
+`
+};
+
+export const variantDemo = {
+ type: 'code',
+ tabs: [
+ {
+ name: 'Linear',
+ code: `
+
+
+ Storage used
+
+
+
+
+`
+ },
+ {
+ name: 'Circular',
+ code: `
+
+
+
+
+
+
+
+
+
+
+
+
+`
+ }
+ ]
+};
+
+export const customizationDemo = {
+ type: 'code',
+ tabs: [
+ {
+ name: 'Linear',
+ code: `
+
+
+
+
+
+
+
+
+
+`
+ },
+ {
+ name: 'Circular',
+ code: `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`
+ }
+ ]
+};
+
+export const withLabelsDemo = {
+ type: 'code',
+ code: `
+
+
+ CPU Usage
+
+
+
+
+
+
+ Memory
+
+
+
+
+`
+};
+
+export const customRangeDemo = {
+ type: 'code',
+ code: `
+
+
+ API Calls
+
+
+
+
+`
+};
diff --git a/apps/www/src/content/docs/components/meter/index.mdx b/apps/www/src/content/docs/components/meter/index.mdx
new file mode 100644
index 00000000..7e18ac50
--- /dev/null
+++ b/apps/www/src/content/docs/components/meter/index.mdx
@@ -0,0 +1,91 @@
+---
+title: Meter
+description: A graphical display of a numeric value within a known range.
+source: packages/raystack/components/meter
+---
+
+import { playground, directUsageDemo, variantDemo, customizationDemo, withLabelsDemo, customRangeDemo } from "./demo.ts";
+
+
+
+## Anatomy
+
+Import and assemble the component:
+
+```tsx
+import { Meter } from '@raystack/apsara'
+
+{/* Direct usage — renders track automatically */}
+
+
+{/* Composable usage */}
+
+ Storage used
+
+
+
+```
+
+## API Reference
+
+### Root
+
+The main container for the meter. Renders a default track when no children are provided.
+
+
+
+### Label
+
+Displays a label for the meter.
+
+
+
+### Value
+
+Displays the formatted current value as a percentage.
+
+
+
+### Track
+
+Contains the indicator that visualizes the current value.
+
+
+
+## Examples
+
+### Variant
+
+The meter supports `linear` and `circular` variants.
+
+
+
+### Direct Usage
+
+The simplest way to use the meter. When no children are provided, it renders the track automatically.
+
+
+
+### Customization
+
+Customize the track for both variants. For linear, use `height` on the track. For circular, use `width`/`height` on the track to control the overall size and `--rs-meter-track-size` to control the stroke thickness.
+
+
+
+### With Labels
+
+Compose with `Meter.Label` and `Meter.Value` for additional context.
+
+
+
+### Custom Range
+
+Use `min` and `max` to define custom value ranges.
+
+
+
+## Accessibility
+
+- Uses the `meter` ARIA role
+- Sets `aria-valuenow`, `aria-valuemin`, and `aria-valuemax` attributes
+- Supports `aria-label` and `aria-valuetext` for screen readers
diff --git a/apps/www/src/content/docs/components/meter/props.ts b/apps/www/src/content/docs/components/meter/props.ts
new file mode 100644
index 00000000..5ca2fa4d
--- /dev/null
+++ b/apps/www/src/content/docs/components/meter/props.ts
@@ -0,0 +1,43 @@
+export interface MeterProps {
+ /** The current value of the meter. */
+ value: number;
+
+ /**
+ * Minimum value.
+ * @default 0
+ */
+ min?: number;
+
+ /**
+ * Maximum value.
+ * @default 100
+ */
+ max?: number;
+
+ /**
+ * The visual style of the meter.
+ * @default "linear"
+ */
+ variant?: 'linear' | 'circular';
+
+ /** Additional CSS class name. */
+ className?: string;
+}
+
+export interface MeterLabelProps {
+ /** The label text content. */
+ children: React.ReactNode;
+
+ /** Additional CSS class name. */
+ className?: string;
+}
+
+export interface MeterValueProps {
+ /** Additional CSS class name. */
+ className?: string;
+}
+
+export interface MeterTrackProps {
+ /** Additional CSS class name. */
+ className?: string;
+}
diff --git a/packages/raystack/components/meter/__tests__/meter.test.tsx b/packages/raystack/components/meter/__tests__/meter.test.tsx
new file mode 100644
index 00000000..a528ac4f
--- /dev/null
+++ b/packages/raystack/components/meter/__tests__/meter.test.tsx
@@ -0,0 +1,119 @@
+import { render, screen } from '@testing-library/react';
+import { describe, expect, it, vi } from 'vitest';
+import { Meter } from '../meter';
+import styles from '../meter.module.css';
+
+describe('Meter', () => {
+ describe('Basic Rendering', () => {
+ it('renders meter element', () => {
+ const { container } = render();
+ const meter = container.querySelector(`.${styles.meter}`);
+ expect(meter).toBeInTheDocument();
+ });
+
+ it('forwards ref correctly', () => {
+ const ref = vi.fn();
+ render();
+ expect(ref).toHaveBeenCalled();
+ });
+
+ it('applies custom className', () => {
+ const { container } = render(
+
+ );
+ const meter = container.querySelector(`.${styles.meter}`);
+ expect(meter).toHaveClass('custom-meter');
+ });
+
+ it('renders track and indicator by default', () => {
+ const { container } = render();
+ expect(container.querySelector(`.${styles.track}`)).toBeInTheDocument();
+ expect(
+ container.querySelector(`.${styles.indicator}`)
+ ).toBeInTheDocument();
+ });
+
+ it('renders default track when no children provided', () => {
+ const { container } = render();
+ expect(container.querySelector(`.${styles.track}`)).toBeInTheDocument();
+ });
+ });
+
+ describe('Variants', () => {
+ it('defaults to linear variant', () => {
+ const { container } = render();
+ const meter = container.querySelector(`.${styles.meter}`);
+ expect(meter).not.toHaveClass(styles['meter-variant-circular']);
+ });
+
+ it('renders circular variant', () => {
+ const { container } = render();
+ const meter = container.querySelector(`.${styles.meter}`);
+ expect(meter).toHaveClass(styles['meter-variant-circular']);
+ });
+
+ it('renders SVG track and indicator for circular variant', () => {
+ const { container } = render();
+ expect(container.querySelector('svg')).toBeInTheDocument();
+ expect(
+ container.querySelector(`.${styles.circularTrackCircle}`)
+ ).toBeInTheDocument();
+ expect(
+ container.querySelector(`.${styles.circularIndicatorCircle}`)
+ ).toBeInTheDocument();
+ });
+ });
+
+ describe('Sub-components', () => {
+ it('renders Label sub-component', () => {
+ render(
+
+ Storage
+
+
+ );
+ expect(screen.getByText('Storage')).toBeInTheDocument();
+ });
+
+ it('renders Value sub-component', () => {
+ render(
+
+
+
+
+ );
+ expect(screen.getByText('50%')).toBeInTheDocument();
+ });
+
+ it('renders custom children instead of default track', () => {
+ const { container } = render(
+
+ Custom
+
+
+ );
+ expect(screen.getByText('Custom')).toBeInTheDocument();
+ expect(container.querySelector(`.${styles.track}`)).toBeInTheDocument();
+ });
+ });
+
+ describe('Accessibility', () => {
+ it('has meter role', () => {
+ render();
+ expect(screen.getByRole('meter')).toBeInTheDocument();
+ });
+
+ it('sets aria-valuenow', () => {
+ render();
+ const meter = screen.getByRole('meter');
+ expect(meter).toHaveAttribute('aria-valuenow', '75');
+ });
+
+ it('sets aria-valuemin and aria-valuemax', () => {
+ render();
+ const meter = screen.getByRole('meter');
+ expect(meter).toHaveAttribute('aria-valuemin', '0');
+ expect(meter).toHaveAttribute('aria-valuemax', '200');
+ });
+ });
+});
diff --git a/packages/raystack/components/meter/index.tsx b/packages/raystack/components/meter/index.tsx
new file mode 100644
index 00000000..313c00a5
--- /dev/null
+++ b/packages/raystack/components/meter/index.tsx
@@ -0,0 +1 @@
+export { Meter } from './meter';
diff --git a/packages/raystack/components/meter/meter-misc.tsx b/packages/raystack/components/meter/meter-misc.tsx
new file mode 100644
index 00000000..5ccb5e85
--- /dev/null
+++ b/packages/raystack/components/meter/meter-misc.tsx
@@ -0,0 +1,32 @@
+'use client';
+
+import { Meter as MeterPrimitive } from '@base-ui/react';
+import { cx } from 'class-variance-authority';
+import { type ElementRef, forwardRef } from 'react';
+import styles from './meter.module.css';
+
+export const MeterLabel = forwardRef<
+ ElementRef,
+ MeterPrimitive.Label.Props
+>(({ className, ...props }, ref) => (
+
+));
+
+MeterLabel.displayName = 'MeterLabel';
+
+export const MeterValue = forwardRef<
+ ElementRef,
+ MeterPrimitive.Value.Props
+>(({ className, ...props }, ref) => (
+
+));
+
+MeterValue.displayName = 'MeterValue';
diff --git a/packages/raystack/components/meter/meter-root.tsx b/packages/raystack/components/meter/meter-root.tsx
new file mode 100644
index 00000000..f791240a
--- /dev/null
+++ b/packages/raystack/components/meter/meter-root.tsx
@@ -0,0 +1,81 @@
+'use client';
+
+import { Meter as MeterPrimitive } from '@base-ui/react';
+import { cva, type VariantProps } from 'class-variance-authority';
+import { createContext, type ElementRef, forwardRef } from 'react';
+import styles from './meter.module.css';
+import { MeterTrack } from './meter-track';
+
+const meter = cva(styles.meter, {
+ variants: {
+ variant: {
+ linear: '',
+ circular: styles['meter-variant-circular']
+ }
+ },
+ defaultVariants: {
+ variant: 'linear'
+ }
+});
+
+export interface MeterProps
+ extends MeterPrimitive.Root.Props,
+ VariantProps {}
+
+export interface MeterContextValue {
+ variant: 'linear' | 'circular';
+ value: number;
+ percentage: number;
+}
+
+export const MeterContext = createContext({
+ variant: 'linear',
+ value: 0,
+ percentage: 0
+});
+
+export const MeterRoot = forwardRef<
+ ElementRef,
+ MeterProps
+>(
+ (
+ {
+ className,
+ style = {},
+ variant = 'linear',
+ children = ,
+ value = 0,
+ min = 0,
+ max = 100,
+ ...props
+ },
+ ref
+ ) => {
+ const percentage = ((value - min) * 100) / (max - min);
+
+ return (
+
+
+ {children}
+
+
+ );
+ }
+);
+
+MeterRoot.displayName = 'MeterRoot';
diff --git a/packages/raystack/components/meter/meter-track.tsx b/packages/raystack/components/meter/meter-track.tsx
new file mode 100644
index 00000000..791d2f25
--- /dev/null
+++ b/packages/raystack/components/meter/meter-track.tsx
@@ -0,0 +1,48 @@
+'use client';
+
+import { Meter as MeterPrimitive } from '@base-ui/react';
+import { cx } from 'class-variance-authority';
+import { type ElementRef, forwardRef, useContext } from 'react';
+import styles from './meter.module.css';
+import { MeterContext } from './meter-root';
+
+export const MeterTrack = forwardRef<
+ ElementRef,
+ MeterPrimitive.Track.Props
+>(({ className, children, ...props }, ref) => {
+ const { variant } = useContext(MeterContext);
+
+ if (variant === 'circular') {
+ return (
+ (
+
+ )}
+ >
+ }
+ />
+ {children}
+
+ );
+ }
+
+ return (
+
+
+ {children}
+
+ );
+});
+
+MeterTrack.displayName = 'MeterTrack';
diff --git a/packages/raystack/components/meter/meter.module.css b/packages/raystack/components/meter/meter.module.css
new file mode 100644
index 00000000..abd50123
--- /dev/null
+++ b/packages/raystack/components/meter/meter.module.css
@@ -0,0 +1,97 @@
+.meter {
+ display: flex;
+ flex-direction: column;
+ gap: var(--rs-space-3);
+ width: 100%;
+}
+
+.header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+}
+
+.label {
+ font-family: var(--rs-font-body);
+ font-size: var(--rs-font-size-mini);
+ font-weight: var(--rs-font-weight-medium);
+ line-height: var(--rs-line-height-mini);
+ letter-spacing: var(--rs-letter-spacing-mini);
+ color: var(--rs-color-foreground-base-primary);
+}
+
+.value {
+ font-family: var(--rs-font-body);
+ font-size: var(--rs-font-size-mini);
+ font-weight: var(--rs-font-weight-regular);
+ line-height: var(--rs-line-height-mini);
+ letter-spacing: var(--rs-letter-spacing-mini);
+ color: var(--rs-color-foreground-base-primary);
+ text-align: right;
+}
+
+.track {
+ position: relative;
+ width: 100%;
+ height: 4px;
+ overflow: clip;
+ border-radius: 1px;
+ background-color: var(--rs-color-background-neutral-secondary);
+}
+
+.indicator {
+ height: 100%;
+ background-color: var(--rs-color-background-accent-emphasis);
+ transition: width 500ms;
+}
+
+/* Circular variant — viewBox is 72×72, SVG scales to container size */
+.meter-variant-circular {
+ align-items: center;
+ justify-content: center;
+ position: relative;
+}
+
+.circularSvg {
+ --rs-meter-track-size: 4px;
+ --rs-meter-radius: calc((72px - var(--rs-meter-track-size) * 2) / 2);
+ --rs-meter-circumference: calc(2 * 3.14159265 * var(--rs-meter-radius));
+ width: 72px;
+ height: 72px;
+ aspect-ratio: 1;
+ transform: rotate(-90deg);
+}
+
+.circularTrackCircle,
+.circularIndicatorCircle {
+ cx: 50%;
+ cy: 50%;
+ r: var(--rs-meter-radius);
+ stroke-width: var(--rs-meter-track-size);
+ fill: none;
+}
+
+.circularTrackCircle {
+ stroke: var(--rs-color-background-neutral-secondary);
+}
+
+.circularIndicatorCircle {
+ stroke: var(--rs-color-background-accent-emphasis);
+ stroke-dasharray: var(--rs-meter-circumference);
+ stroke-dashoffset: calc(
+ var(--rs-meter-circumference) *
+ (1 - var(--rs-meter-percentage, 0) / 100)
+ );
+ stroke-linecap: butt;
+ transition: stroke-dashoffset 500ms;
+}
+
+.meter-variant-circular .value {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ font-weight: var(--rs-font-weight-medium);
+ text-align: center;
+ white-space: nowrap;
+}
diff --git a/packages/raystack/components/meter/meter.tsx b/packages/raystack/components/meter/meter.tsx
new file mode 100644
index 00000000..51a5d595
--- /dev/null
+++ b/packages/raystack/components/meter/meter.tsx
@@ -0,0 +1,9 @@
+import { MeterLabel, MeterValue } from './meter-misc';
+import { MeterRoot } from './meter-root';
+import { MeterTrack } from './meter-track';
+
+export const Meter = Object.assign(MeterRoot, {
+ Label: MeterLabel,
+ Value: MeterValue,
+ Track: MeterTrack
+});
diff --git a/packages/raystack/index.tsx b/packages/raystack/index.tsx
index 95a9e531..d9fe0a56 100644
--- a/packages/raystack/index.tsx
+++ b/packages/raystack/index.tsx
@@ -43,6 +43,7 @@ export { Label } from './components/label';
export { Link } from './components/link';
export { List } from './components/list';
export { Menu } from './components/menu';
+export { Meter } from './components/meter';
export { Navbar } from './components/navbar';
export { Popover } from './components/popover';
export { PreviewCard } from './components/preview-card';