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
- shadcn registry — drop production components in:
npx shadcn@latest add https://riyal.js.org/r/riyal-checkout-summary.json - Live demo: https://riyal.js.org
- npm: npmjs.com/package/riyal
Built by Pooya Golchian · riyal on npm · live demo