Skip to content
Twilight React Playground
ثيم رائدaren

Money, dates and numbers

Beginner9 min

Format prices, dates and digits the way the store and the page language expect, with useMoney, useDate and useNumber.

1250 is not what a shopper should read. On this store's Arabic pages a price is written with two decimals, Arabic-Indic digits and the riyal sign, and a date reads "16 سبتمبر 2026" rather than "2026-09-16". Three hooks do this for you, following the page language and the store's settings:

  • useMoney() formats prices;
  • useDate() formats dates, and "3 hours ago";
  • useNumber() writes counts and other numbers with the digits the store chose.

All three on one line

Change the quantity, then switch the ar / en pill (top bar, or the ☰ menu on a phone). This store turned Arabic numbers on, so watch which values change digits in English, and read the last row carefully.

quantity 3
× 3686.55
September 16, 2026
useArabicNumerals
false (a store setting)
'Total: ' + format(total)
Total: [object Object]
app/components/OrderLine.tsx
import { useDate, useNumber } from '@salla.sa/twilight-theme-engine/hooks';
import { useMoney } from '@salla.sa/twilight-theme-engine/hooks/useMoney';

export function OrderLine({ quantity, unitPrice, orderedAt }: {
  quantity: number;
  unitPrice: number;
  orderedAt: string;
}) {
  const { format } = useMoney();
  const { format: formatNumber } = useNumber();
  const { format: formatDate } = useDate();

  return (
    <div className="order-line">
      <span>× {formatNumber(quantity)}</span>
      <strong>{format(unitPrice * quantity)}</strong>
      <small>{formatDate(orderedAt, 'long')}</small>
    </div>
  );
}

Prices: useMoney

useMoney() formats the amount with the store settings the page was rendered with.Try this: set the currency to USD, then clear the amount and switch the type between product and general.
Storefront canvas · en · LTR

1,250.00

isValid(amount)
true
parse('1,250.50 SAR')
1250.5
Controls
A number or a numeric string. Try an empty value.
Only matters for an empty amount.
What a theme writes
import { useMoney } from '@salla.sa/twilight-theme-engine/hooks/useMoney';

export function Price() {
  const { format } = useMoney();
  return <span className="price">{format(1250)}</span>;
}
  • Always two decimal places, grouped the way the language groups thousands.
  • The currency defaults to SAR, whatever the store's currency is. Pass { currency } when you show another one.
  • Arabic-Indic digits appear only for riyals, only on Arabic pages, and only when the store turned Arabic numbers on.

Dates: useDate

useDate() formats a date and a "time ago" in the page language. It runs in the browser here because ago() reads the clock.Try this: set minutes to 1440 (yesterday), then switch the language pill between ar and en. Clear the date to see format() fall back to today.
Storefront canvas · en · LTR
Runs in the browser…
Controls
An ISO date string. Try an empty value.
What a theme writes
import { useDate, useIsClient } from '@salla.sa/twilight-theme-engine/hooks';

export function PostDate({ publishedAt = '2024-12-31T10:30:00' }: { publishedAt?: string }) {
  const { format, ago } = useDate();
  const isClient = useIsClient(); // relative time depends on the clock: render it after hydration
  return (
    <p>
      <time dateTime={publishedAt}>{format(publishedAt)}</time>
      {isClient && <span> · {ago(publishedAt)}</span>}
    </p>
  );
}

format(date, 'full' | 'long' | 'medium' | 'short' | 'time') accepts a date string, a timestamp, a Date, or the { date, timezone } objects some Salla responses use. ago(date) says how long ago in words.

Numbers: useNumber

useNumber() swaps digits for Arabic-Indic ones when the store turns Arabic numbers on. This demo store does.Try this: switch the language pill to en: format() still returns Arabic-Indic digits, because it never looks at the language.
Storefront canvas · en · LTR
useArabicNumerals
false
format(value)
Order #1250, 3 items
toArabic(value)
Order #١٢٥٠, ٣ items
Controls
A number or any text containing digits.
What a theme writes
import { useNumber } from '@salla.sa/twilight-theme-engine/hooks';

export function OrderLabel() {
  const { format } = useNumber();
  return <span>{format('Order #1250, 3 items')}</span>;
}
In engine terms
  • useMoney has its own subpath, @salla.sa/twilight-theme-engine/hooks/useMoney; useDate and useNumber are exported only from the /hooks barrel.
  • useMoney().format uses Intl.NumberFormat(locale, { minimumFractionDigits: 2, maximumFractionDigits: 2 }), cached per locale and options, then swaps the digits when store.settings.arabic_numbers_enabled is on and the locale starts with ar. SAR returns a fragment with an sicon-sar icon; any other code is formatted again with style: 'currency', which skips the digit swap (src/hooks/useMoney.tsx).
  • An empty amount returns '-' only for type: 'product' when store.settings.product.show_price_as_dash is on, otherwise ''.
  • useNumber().format ignores the language: it swaps digits whenever the store setting is on, and adds no thousands separator (src/hooks/useNumber.ts).
  • useDate uses Intl.DateTimeFormat with no time zone, so the server formats in UTC and the browser in the shopper's zone: a time close to midnight can land on different days in the two renders. ago() and now read the clock. Render any of those after hydration, with useIsClient().
  • Reference: useMoney, useDate, useNumber.
Check yourself

What does useMoney().format(99, { currency: 'USD' }) return on an English page?