@fadymondy/nasaq
v1.4.0
Published
Nasaq (نسق): one product language for every surface. React + Base UI + Tailwind v4 components, the same components for Vue 3 and for HTML/Alpine.js (Laravel Blade, Livewire, FilamentPHP via fadymondy/nasaq-php), design tokens, brand marks, a React Native
Maintainers
Readme
@fadymondy/nasaq
Nasaq (نسق) is one product language for every surface: React 19 components built on Base UI and Tailwind CSS v4, design tokens, brand marks, a React Native kit and Electron window chrome. Arabic and RTL are first-class, not an afterthought.
- Hundreds of components: buttons, forms, data tables, charts, commerce, auth, AI, workflow, and full page blocks.
- Tokens as CSS variables (
--nq-*), themeable per brand (data-brand), light and dark, three densities. - Logical properties everywhere: the same component is correct in English and Arabic.
- Also on the shadcn registry, if you prefer to own the source (see below).
Docs and live component lab: https://docs.nasaqui.com · Landing: https://nasaqui.com
Install
npm install @fadymondy/nasaq react react-dom
npm install tailwindcss @tailwindcss/postcss # Tailwind v4 (or @tailwindcss/vite)Requires React 19+ and Tailwind CSS 4. react-native, react-native-svg, expo-haptics and electron are optional peers, needed only for /native and /electron.
The Vue and Alpine adapters' libraries are optional peers too, so a React app installs no Vue or Alpine code. Add them only for the entry point you use:
npm install vue reka-ui lucide-vue-next vue-sonner # @fadymondy/nasaq/vue
npm install alpinejs @alpinejs/anchor @alpinejs/collapse @alpinejs/focus # @fadymondy/nasaq/alpineThe script-tag build (cdn/nasaq-alpine.js) bundles the Alpine plugins and needs neither.
There is no root import. Use the subpaths listed below.
CSS setup (required)
The components use Tailwind utility classes and --nq-* tokens. Tailwind does not scan node_modules, so tell it where the package lives. In your Tailwind entry CSS (for Next App Router, app/globals.css):
@import "tailwindcss";
@import "@fadymondy/nasaq/tokens.css";
@import "@fadymondy/nasaq/theme.css";
@import "@fadymondy/nasaq/web/styles.css";
@source "../node_modules/@fadymondy/nasaq/dist/web";The @source path is relative to the CSS file: adjust the ../ so it reaches your node_modules (in a monorepo, the hoisted node_modules). Without the @source line the components render unstyled, because their classes are never generated.
tokens.css: the--nq-*variables (colour, type, spacing, density, expression, brand).theme.css: maps the tokens into Tailwind's@theme(sobg-card,text-muted-foreground,rounded-card,h-controlwork) and thedark:variant.web/styles.css: base styles (fonts, scrollbars, focus).
Provider and no-flash theme
// app/layout.tsx (Server Component)
import { NasaqProvider, nasaqThemeScript } from "@fadymondy/nasaq/web";
import "./globals.css";
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en" suppressHydrationWarning>
<head>
<script dangerouslySetInnerHTML={{ __html: nasaqThemeScript() }} />
</head>
<body>
<NasaqProvider brand="nasaq">{children}</NasaqProvider>
</body>
</html>
);
}Under a strict Content Security Policy an inline script needs a nonce or a hash. Two options:
// 1. A nonce: spread the props and pass the per-request nonce (it must be in script-src).
import { nasaqThemeScriptProps } from "@fadymondy/nasaq/web";
<script {...nasaqThemeScriptProps("system", { nonce })} />
// 2. A static file, allowed by script-src 'self': no nonce, no inline code.
<script src="/nasaq-theme.js" data-default-theme="system" />The static file ships as @fadymondy/nasaq/theme-script.js. Copy it to your public folder (or serve it from
node_modules/@fadymondy/nasaq/dist/theme-script.js). It reads the default theme from data-default-theme and must be a
classic, synchronous script in <head> (no async or defer) so it runs before first paint.
NasaqProvider sets data-brand, data-theme, data-density, data-expression, dir and lang on <html> and persists the theme.
Fonts
The package ships no font files. The stacks are Inter, Alexandria and JetBrains Mono (all SIL OFL, so you can load them with next/font or @fontsource/*) and Lusail, which is proprietary and is not bundled: load your own licensed copy or leave it out and Inter takes over. Without any of them, the system fonts are used.
A first component
import { Button, Card, CardContent, CardHeader, CardTitle, Price } from "@fadymondy/nasaq/web";
export default function Page() {
return (
<Card>
<CardHeader><CardTitle>Nasaq mug</CardTitle></CardHeader>
<CardContent className="flex items-center gap-3">
<Button>Add to cart</Button>
<Price amount={49} currency="SAR" />
</CardContent>
</Card>
);
}Components that hold state or use hooks are marked "use client" in the built files, so you can render them straight from a Server Component. Props that are functions can only be passed from a Client Component (normal React rule). Hook-based APIs such as useDataTable need a "use client" file of your own:
"use client";
import { DataTable, useDataTable, type DataTableColumn } from "@fadymondy/nasaq/web";
const columns: DataTableColumn<Row>[] = [
{ id: "name", header: "Name", cell: (r) => r.name, sortValue: (r) => r.name },
];
export function Customers({ rows }: { rows: Row[] }) {
const table = useDataTable({ data: rows, columns, getRowId: (r) => r.id });
return <DataTable table={table} label="Customers" />;
}Every component has a manual with props, anatomy and examples in the lab and in the MCP (below).
RTL and Arabic
Pass a locale and everything flips: direction, logical spacing, mirrored directional icons, Arabic type metrics.
<NasaqProvider locale="ar">{children}</NasaqProvider> // dir="rtl" lang="ar" on <html>Built-in strings ship in English and Arabic; components take a labels prop to override. Keep code, IDs, URLs and numbers-with-units left-to-right with dir="ltr" or <bdi>.
Theming by brand
<NasaqProvider brand="mahaam" density="comfortable" expression="native" defaultTheme="dark">Brands: nasaq, fadymondy, mahaam, zekra, moharrik, seatfor, health-debug, circlexo, hosbah, orchestra, togo, and the CircleXO apps matjar, sanduq, mizan, qaima, makhzan, mawared. A brand changes identity and action colour only; status colours keep their meaning. Every pair is contrast-tested. Read the manifests and draw marks from @fadymondy/nasaq/brands.
Not one of them? Theme Nasaq with your own colours, no registration: <NasaqProvider brandColors={{ brand: "#C8283A", accent: "#C9A227" }}> (each role is "#RRGGBB" or { light, dark }; dark steps and on-colours are derived for contrast; data-brand="custom"). Pass your own logo to SidebarBrand, AuthLayout or ProductMark src. The native NasaqProvider takes the same brandColors. See Theming in the lab docs.
Subpaths
| Import | What |
| --- | --- |
| @fadymondy/nasaq/web | React DOM components, NasaqProvider, nasaqThemeScript, nasaqThemeScriptProps, cn, commerce helpers |
| @fadymondy/nasaq/theme-script.js | The no-flash theme script as a static file, for strict CSPs |
| @fadymondy/nasaq/tokens | Typed token values (colour, space, type, density, contrast helpers) |
| @fadymondy/nasaq/brands | Brand manifests, mark specs, markSvg() |
| @fadymondy/nasaq/native | React Native kit (StyleSheet, no styling runtime): provider, Text, Button, Screen, ProductMark |
| @fadymondy/nasaq/electron | Renderer title bar, drag regions, useWindowChrome() |
| @fadymondy/nasaq/electron/main | Main process: windowChrome() window options, updateTitleBarOverlay() |
| @fadymondy/nasaq/electron/preload | Preload: exposeChrome() |
| @fadymondy/nasaq/tokens.css | Token CSS variables |
| @fadymondy/nasaq/theme.css | Tailwind v4 @theme mapping |
| @fadymondy/nasaq/web/styles.css | Web base styles |
| @fadymondy/nasaq/electron/chrome.css | Electron drag-region CSS |
| @fadymondy/nasaq/vue | Vue 3 components (NqButton, NqDialog, …) on Reka UI, NasaqProvider, the Nasaq plugin |
| @fadymondy/nasaq/alpine | Alpine.js plugin that drives the HTML the Blade components render (nqDialog, nqTabs, …, $nq, x-nq-money) |
| @fadymondy/nasaq/html | formatMoney and the locale helpers for plain JS |
| @fadymondy/nasaq/nasaq.css | Precompiled stylesheet: tokens, theme and every component class. For apps without Tailwind |
| @fadymondy/nasaq/nasaq.unlayered.css | The same without preflight or cascade layers, for Filament and Tailwind v3 hosts |
| @fadymondy/nasaq/cdn/nasaq-alpine.js | Script-tag build of the Alpine plugin; registers itself on alpine:init |
ESM only, with TypeScript declarations. Heavy libraries (recharts, shiki, tiptap, xyflow, dnd-kit) are regular dependencies and are loaded only when the components that use them are imported; use a bundler that tree-shakes ESM (Next, Vite, esbuild, Rspack).
Not on React? Vue, Inertia, Laravel, Filament and plain HTML
Every stack renders the same markup with the same Tailwind classes, so a screen looks the same in all of them.
// Vue 3 / Nuxt (and Inertia + Vue)
import { Nasaq } from "@fadymondy/nasaq/vue";
createApp(App).use(Nasaq).mount("#app"); // <NqButton>, <NqDialog>, …# Laravel, Livewire, Filament, TomatoPHP: <x-nq::button>, <x-nq::dialog>, …
composer require fadymondy/nasaq-php
php artisan vendor:publish --tag=nasaq-assets # then @nasaqStyles @nasaqScripts, or ->plugin(NasaqPlugin::make())<!-- Any page, no build step -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fadymondy/nasaq/dist/nasaq.css">
<script defer src="https://cdn.jsdelivr.net/npm/@fadymondy/nasaq/dist/cdn/nasaq-alpine.js"></script>
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3/dist/cdn.min.js"></script>Laravel + Inertia uses this package directly (React or Vue). The setup for every stack is on Get started, and each component page shows its code for React, shadcn, Vue, Blade and HTML + Alpine. The MCP server serves the same examples.
Prefer to own the code? Use the shadcn registry
The same components are published as a shadcn registry: files are copied into your app and depend only on Base UI, cva, clsx, tailwind-merge and lucide.
// components.json
{ "registries": { "@nasaq": "https://docs.nasaqui.com/r/{name}.json" } }npx shadcn@latest add @nasaq/buttonIndex: https://docs.nasaqui.com/registry.json
MCP for AI assistants
@fadymondy/nasaq-mcp lets an assistant list components and read their manuals, source, stories and tokens.
npx -y @fadymondy/nasaq-mcp # stdio
# hosted (streamable HTTP): https://mcp.nasaqui.com/mcpLinks
- Docs and lab: https://docs.nasaqui.com
- Site: https://nasaqui.com
- Source and issues: https://github.com/fadymondy/nasaq
Maintainers: publishing
The first publish of each package is done by hand (npm only lets you attach a trusted publisher to a package that exists); later releases are automated by .github/workflows/release.yml (changesets, OIDC provenance).
pnpm install --frozen-lockfile
pnpm typecheck && pnpm lint && pnpm test && pnpm check:components
pnpm check:package # build + publint + attw + use-client check
npm login
pnpm release # builds both packages, then `changeset publish` (publishes what is not on npm yet)
git push --follow-tagsMIT © Fady Mondy
