Skip to content

Cooldown Bar

A slim progress bar with a label and monospace countdown for command cooldowns.

A slim progress bar with a label and monospace countdown for command cooldowns. Compose it from the exported registry parts so local content and behaviour stay in the application while the visual contract stays token-driven.

Root class newt-cooldown
import {
  CooldownBar,
  CooldownBarLabel,
  CooldownBarTrack,
  CooldownBarValue,
} from "@/registry/bases/newt/ui/cooldown-bar"

export default function CooldownBarDemo() {
  return (
    <CooldownBar className="w-full">
      <CooldownBarLabel>
        <span>Command cooldown</span>
        <CooldownBarValue>3.2s</CooldownBarValue>
      </CooldownBarLabel>
      <CooldownBarTrack value={40} aria-label="Command cooldown" />
    </CooldownBar>
  )
}
<script setup lang="ts">
import {
  CooldownBar,
  CooldownBarLabel,
  CooldownBarTrack,
  CooldownBarValue,
} from "~~/registry/bases/newt/ui/cooldown-bar"
</script>

<template>
  <CooldownBar class="w-full">
    <CooldownBarLabel>
      <span>Command cooldown</span>
      <CooldownBarValue>3.2s</CooldownBarValue>
    </CooldownBarLabel>
    <CooldownBarTrack :value="40" aria-label="Command cooldown" />
  </CooldownBar>
</template>

Installation

pnpm dlx newtui@latest add cooldown-bar
npx newtui@latest add cooldown-bar
yarn dlx newtui@latest add cooldown-bar
bunx newtui@latest add cooldown-bar

Copy the source into your project

import * as React from "react"

import { cn } from "@/lib/utils"

const CooldownBar = React.forwardRef<
  HTMLDivElement,
  React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
  <div
    ref={ref}
    className={cn("flex max-w-[260px] flex-col gap-1.5", className)}
    {...props}
  />
))
CooldownBar.displayName = "CooldownBar"

const CooldownBarLabel = React.forwardRef<
  HTMLDivElement,
  React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
  <div
    ref={ref}
    className={cn(
      "flex justify-between text-xs text-newt-text-muted",
      className
    )}
    {...props}
  />
))
CooldownBarLabel.displayName = "CooldownBarLabel"

const CooldownBarValue = React.forwardRef<
  HTMLElement,
  React.HTMLAttributes<HTMLElement>
>(({ className, ...props }, ref) => (
  <strong
    ref={ref}
    className={cn(
      "font-mono font-semibold text-newt-text-secondary",
      className
    )}
    {...props}
  />
))
CooldownBarValue.displayName = "CooldownBarValue"

export interface CooldownBarTrackProps extends React.HTMLAttributes<HTMLDivElement> {
  /** Fill percentage, 0-100. */
  value: number
  /** Switch the fill to the "ready" (online green) colour. */
  ready?: boolean
}

const CooldownBarTrack = React.forwardRef<
  HTMLDivElement,
  CooldownBarTrackProps
>(({ className, value, ready = false, ...props }, ref) => {
  const clamped = Math.min(100, Math.max(0, value))
  return (
    <div
      ref={ref}
      role="progressbar"
      aria-valuemin={0}
      aria-valuemax={100}
      aria-valuenow={clamped}
      data-state={ready ? "ready" : "cooling"}
      className={cn(
        "h-1.5 overflow-hidden rounded-full bg-newt-bg-base",
        className
      )}
      {...props}
    >
      <div
        className={cn(
          "h-full rounded-full",
          ready ? "bg-newt-online" : "bg-newt-idle"
        )}
        style={{ width: `${clamped}%` }}
      />
    </div>
  )
})
CooldownBarTrack.displayName = "CooldownBarTrack"

export { CooldownBar, CooldownBarLabel, CooldownBarValue, CooldownBarTrack }
<script setup lang="ts">
import type { HTMLAttributes } from "vue"

import { cn } from "@/lib/utils"

const props = defineProps<{ class?: HTMLAttributes["class"] }>()
</script>

<template>
  <div :class="cn('flex max-w-[260px] flex-col gap-1.5', props.class)">
    <slot />
  </div>
</template>
<script setup lang="ts">
import type { HTMLAttributes } from "vue"

import { cn } from "@/lib/utils"

const props = defineProps<{ class?: HTMLAttributes["class"] }>()
</script>

<template>
  <div
    :class="
      cn('flex justify-between text-xs text-newt-text-muted', props.class)
    "
  >
    <slot />
  </div>
</template>
<script setup lang="ts">
import type { HTMLAttributes } from "vue"

import { cn } from "@/lib/utils"

const props = defineProps<{ class?: HTMLAttributes["class"] }>()
</script>

<template>
  <strong
    :class="cn('font-mono font-semibold text-newt-text-secondary', props.class)"
  >
    <slot />
  </strong>
</template>
<script setup lang="ts">
import { computed, type HTMLAttributes } from "vue"

import { cn } from "@/lib/utils"

const props = defineProps<{
  class?: HTMLAttributes["class"]
  /** Fill percentage, 0-100. */
  value: number
  /** Switch the fill to the "ready" (online green) colour. */
  ready?: boolean
}>()

const clamped = computed(() => Math.min(100, Math.max(0, props.value)))
</script>

<template>
  <div
    role="progressbar"
    :aria-valuemin="0"
    :aria-valuemax="100"
    :aria-valuenow="clamped"
    :data-state="props.ready ? 'ready' : 'cooling'"
    :class="
      cn('h-1.5 overflow-hidden rounded-full bg-newt-bg-base', props.class)
    "
  >
    <div
      :class="
        cn(
          'h-full rounded-full',
          props.ready ? 'bg-newt-online' : 'bg-newt-idle'
        )
      "
      :style="{ width: `${clamped}%` }"
    />
  </div>
</template>
export { default as CooldownBar } from "./CooldownBar.vue"
export { default as CooldownBarLabel } from "./CooldownBarLabel.vue"
export { default as CooldownBarValue } from "./CooldownBarValue.vue"
export { default as CooldownBarTrack } from "./CooldownBarTrack.vue"

Update the import paths

The files import @/lib/utils and sibling components through the aliases in your components.json. Point them at wherever those live in your project.

Usage

import { CooldownBar } from "@/components/ui/cooldown-bar"

export function Example() {
  return <CooldownBar />
}
<script setup lang="ts">
import { CooldownBar, CooldownBarLabel, CooldownBarValue, CooldownBarTrack } from "@/components/ui/cooldown-bar"
</script>

<template>
  <CooldownBar />
</template>

API reference

CooldownBarTrack

PropType
value?number

Fill percentage, 0-100.

Typenumber
ready?boolean | undefined

Switch the fill to the "ready" (online green) colour.

Typeboolean | undefined

The registry exports CooldownBar, CooldownBarLabel, CooldownBarValue, CooldownBarTrack. Parts without a table accept the native attributes for the element they render; React uses className, while Vue uses class and fallthrough attributes.

Accessibility

Preserve the native semantics of the rendered elements and pass through ARIA attributes when the surrounding context needs a label or description. Any interactive child needs a visible focus indicator, and meaning must not rely on colour alone.

Tokens

Cooldown Bar reads its colours, radii and motion from the --newt-* tokens installed with the library. Override a token and every component that reads it follows, including this one — see the token layer. In the plain HTML and CSS distribution the same component is newt-cooldown.