⃁ Riyal

Guide · 4 min read · Updated 2026-05-08

How to display the Saudi Riyal symbol (U+20C1) in Next.js

Use riyal/next + riyal/react for zero-CLS embedding of the official SAMA Saudi Riyal sign in Next.js 13+ apps. Server Components, Client Components, OG image cards, and a checkout-grade masked input all in one package.

Next.js 13+ moved type rendering to next/font and split components into Server vs Client. riyal/next wires the bundled SAMA glyph into next/font/local for zero CLS, and riyal/react exposes Server-Component-safe and Client-Component-only primitives so you pick the right one.

Install

pnpm add riyal next react react-dom

next ≥ 13 and react ≥ 18 are optional peer dependencies.

Wire the font once in app/layout.tsx

import { riyalFont } from "riyal/next";
import "riyal/css";

const font = riyalFont({ display: "swap" });

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en" className={font.className}>
      <body>{children}</body>
    </html>
  );
}

riyalFont wraps next/font/local with the bundled WOFF2 + Regular/Medium/Bold weights, served self-host (no third-party request) with font-display: swap by default.

Server Components

Render prices anywhere — they’re SSR-safe, hydration-clean, and work in async Server Components:

// app/(shop)/[product]/page.tsx — Server Component, no "use client"
import { RiyalPrice } from "riyal/react";

export default async function ProductPage({ params }: { params: { product: string } }) {
  const product = await fetchProduct(params.product);
  return (
    <article>
      <h1>{product.title}</h1>
      <p><RiyalPrice amount={product.priceSAR} /></p>
    </article>
  );
}

Client Components — masked input

RiyalInput, AnimatedRiyalPrice, and useRiyalRate use useState / requestAnimationFrame and need a "use client" directive:

"use client";
import { useState } from "react";
import { RiyalInput } from "riyal/react";

export function CheckoutForm() {
  const [amount, setAmount] = useState<number | "">(0);
  return <RiyalInput mask value={amount} onValueChange={setAmount} />;
}

OG image cards

Pair riyal/og with next/og to bake the SAMA glyph into share images for social platforms:

// app/og/route.tsx
import { ImageResponse } from "next/og";
import { RiyalPriceCard } from "riyal/og";

export async function GET() {
  return new ImageResponse(
    <RiyalPriceCard title="Pricing" amount={2499.99} locale="ar-SA" />,
    { width: 1200, height: 630 },
  );
}

The card is Satori-ready and renders the glyph inline, so Twitter / LinkedIn / etc. don’t need to load any font.

Cart math & VAT

import { lineItem, cartTotal } from "riyal/cart";

const totals = cartTotal(items, { shipping: 20, discount: 10 });
// totals.netTotal, totals.vat, totals.total — Saudi-15%-VAT defaults

Next steps


Built by Pooya Golchian · riyal on npm · live demo