@konce-pt/react
v0.9.1
Published
Open-source React 19 component library on the Koncept UI design system — the same kpt-* CSS layer and --kpt-* tokens as the Angular port. Zero runtime dependencies.
Maintainers
Readme
Koncept UI — React
Open-source React 19 component library on the Koncept UI design system. 80+ components sharing the exact CSS layer with the Angular port — zero runtime dependencies.
Why Koncept UI React
Koncept UI is a free, MIT-licensed design system. This package is its React 19 port — not a lookalike, but the same rendering contract:
- 🎯 One CSS layer for both ports — the same
kpt-*classes, the same--kpt-*tokens, the same@layer kpt.*ordering. The DOM tree matches the Angular port node for node, so an app can mix the two and nothing shifts by a pixel. - 📦 Zero runtime dependencies — no
clsx, noradix, no CSS-in-JS.cn,SlotandcomposeRefsship with the package. - ⚛️ React 19 native —
refis a plain prop (noforwardRef), and every form control renders a nativename/form, so<form action={…}>Actions and React Hook Form'sregister()work without an adapter. Controls are usable controlled and uncontrolled. - 📊 Flagship data table — sorting, global and per-column filters, pagination, virtual scroll (5000+ rows), selection, column reorder & freeze, inline editing, summary row, CSV export.
- 🎨 100% tokenized — every style is a
var(--kpt-*)custom property. Light/dark theme, neutral OKLCH palette. Rebrand by swapping one token layer. - ♿ Native elements first —
<button>,<input>,<label>wherever one exists, so keyboard and screen-reader behaviour comes from the platform. Invalid state travels onaria-invalid, never on a class. - 🌍 i18n built in — English (default) & Polish out of the box, runtime locale switch, add any language with one JSON.
Installation
npm i @konce-pt/react @konce-pt/tokens @konce-pt/styles
# or: pnpm add / yarn addreact and react-dom (>=19) are peers you already have. Optional extras:
@konce-pt/grid— the mobile-first layout system; its React components live in@konce-pt/react/grid.@konce-pt/validators— Polish-market validators (NIP, REGON, PESEL, IBAN, postal code).leaflet(^1.9) — an optional peer, needed only by@konce-pt/react/map, loaded through a dynamicimport().
Setup
The package ships JavaScript only. Load the shared CSS layer once, in your app entry:
import '@konce-pt/tokens/css';
import '@konce-pt/tokens/css/dark';
import '@konce-pt/styles'; // reset, @layer ordering, theme
import '@konce-pt/styles/components'; // the component layer — REQUIRED, this is what styles kpt-*
import '@konce-pt/grid'; // optional — only if you use the layout classes@konce-pt/styles/components is generated from the same SCSS the Angular port compiles into its own
bundle, which is why both ports render identically. The Angular package injects those styles at
runtime; React has no other source for them, so this import is not optional.
Dark theme: set <html data-theme="dark"> (or rely on prefers-color-scheme).
No CDK here. Components with a floating panel (
KptSelect,KptPopover,KptMenu,KptAutocomplete,KptDatepicker…) portal intodocument.bodythrough the internalKptOverlay, so@angular/cdk/overlay-prebuilt.cssis not needed — positioning lives in the component, the look in@konce-pt/styles/components. Inside an openKptDialogthose panels portal into the<dialog>instead, becauseshowModal()moves it to the browser top layer.
Quick start
A login form — controlled values, the error gated on touch, no form library required:
import { useState } from 'react';
import { KptButton, KptFormField, KptInput } from '@konce-pt/react';
export function Login() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [touched, setTouched] = useState<Record<string, boolean>>({});
const emailError = !email.includes('@') ? 'Enter a valid e-mail address' : null;
const passwordError = password.length < 8 ? 'At least 8 characters' : null;
const invalid = Boolean(emailError || passwordError);
return (
<form
onSubmit={(e) => {
e.preventDefault();
console.log({ email, password });
}}
>
<KptFormField label="E-mail" error={touched.email ? emailError : null} required>
<KptInput
type="email"
name="email"
value={email}
onValueChange={setEmail}
onTouch={() => setTouched((t) => ({ ...t, email: true }))}
invalid={Boolean(emailError)}
/>
</KptFormField>
<KptFormField label="Password" error={touched.password ? passwordError : null} required>
<KptInput
type="password"
name="password"
value={password}
onValueChange={setPassword}
onTouch={() => setTouched((t) => ({ ...t, password: true }))}
invalid={Boolean(passwordError)}
/>
</KptFormField>
<KptButton type="submit" disabled={invalid}>Sign in</KptButton>
</form>
);
}KptFormField generates the control id and publishes it through context, so label[for] and
input[id] match with no configuration. Drop value/onValueChange and the field becomes
uncontrolled — the value lives in the DOM, and a native <form action={…}> or React Hook Form's
register() picks it up as-is.
Data table with sorting, filtering, pagination and a custom cell:
<KptDataTable
columns={columns}
data={users}
filterable
exportable
pageSize={10}
selectable="multiple"
rowKey="id"
selection={selected}
onSelectionChange={setSelected}
cells={{ status: (row, value) => <KptBadge value={String(value)} /> }}
/>Components (80+)
| Category | Components |
| --- | --- |
| Forms | KptInput, KptTextarea, KptSelect, KptAutocomplete, KptCheckbox, KptSwitch, KptSwitchGroup, KptRadioGroup, KptSlider, KptRating, KptDatepicker, KptDateRange, KptClock, KptColorPicker, KptFileUpload, KptInputNumber, KptPassword, KptInputOtp, KptChipsInput, KptInputMask, KptListbox, KptKnob, KptRichText, KptFormField |
| Buttons & actions | KptButton, KptIconButton, KptButtonGroup, KptFab, KptSplitButton, KptSpeedDial |
| Layout | KptAppShell, KptToolbar, KptSidenav, KptCard (+ Header, Footer, Media, Title…), KptDivider, KptPanel, KptFieldset, KptSplitter, KptScrollTop |
| Navigation | KptTabs, KptAccordion, KptBreadcrumb, KptStepper, KptMenu, KptMenubar, KptMegamenu, KptContextMenu |
| Data | KptDataTable, KptPaginator, KptTree, KptTimeline, KptCarousel, KptDataView, KptPickList, KptOrderList, KptGalleria, KptMeterGroup |
| Feedback & overlay | KptAlert, KptDialog, kptToast + KptToastContainer, KptTooltip, KptPopover, KptDrawer, KptBottomSheet, kptConfirm, KptBadge, KptChip, KptAvatar, KptAvatarGroup, KptSpinner, KptProgress, KptSkeleton, KptEmpty, KptImage, KptAuth |
| Subpaths | @konce-pt/react/charts (KptChart), /chat (KptChat, KptChatMessage, KptChatTyping, KptChatComposer, KptChatDock), /roadmap (KptRoadmap), /grid (KptGrid, KptCol, KptFlex), /map (KptMap + legend, POI card, search), /icons (full Tabler set) |
The heavy pieces (charts, chat, roadmap, maps, the full icon set) live in subpaths on purpose, so the core
entry point stays small. Every component ships an llms.txt API sheet next to its source.
Theming
All visuals are driven by var(--kpt-*) tokens (three tiers: primitives → semantic → component).
Override them in a stylesheet imported after @konce-pt/styles:
:root {
--kpt-color-primary: oklch(0.55 0.2 265); /* rebrand in one line */
--kpt-color-primary-hover: oklch(0.5 0.2 265);
}
:root[data-theme='dark'] {
--kpt-color-primary: oklch(0.7 0.16 265);
}Common token names: surfaces --kpt-color-surface, --kpt-color-surface-sunken|raised|variant|hover|selected; text --kpt-color-on-surface, --kpt-color-on-surface-muted (aliases --kpt-color-text, --kpt-color-text-muted|subtle|inverse); borders --kpt-color-border, --kpt-color-border-strong; accent roles in full — {primary,danger,success,warning,info} each with -hover, -contrast, -subtle, -border; radii --kpt-radius-sm|md|lg|xl|full|none; elevation --kpt-elevation-1..4 (aliases --kpt-shadow-sm|md|lg). There is no --kpt-color-bg.
Variants and sizes travel on data-* attributes (data-variant, data-size), never on modifier
classes — that is what the SCSS reads.
Icons
84 icons are built in and need no configuration — what the components themselves draw plus the
staples of an application shell (sun, moon, bell, settings, users, logout,
layout-dashboard, home, activity, chart-bar, brand-github …). A typical admin panel needs
nothing else.
For the full Tabler Icons set (MIT, 5130 icons) there are two paths, and the difference is the bundle. Every icon is its own export, so importing by name lets the bundler keep just those:
// Production — two icons reach the bundle.
import { LayoutDashboard, Rocket } from '@konce-pt/react/icons';
import { registerKptIcons } from '@konce-pt/react';
registerKptIcons([LayoutDashboard, Rocket]);
// Prototype — every icon, about 1.2 MB.
import { registerKptTablerIcons } from '@konce-pt/react/icons';
registerKptTablerIcons();registerKptIcons is the one that shrinks: it takes the data as an argument and imports nothing from
the Tabler subpath. For the same reason registerKptIcons(tabler) with a namespace import
(import * as tabler) registers everything — a namespace object handed to a function blocks static
analysis. The registry is global, so one call at the app entry is enough; no provider needed.
Internationalization (i18n)
Component labels ship in English (default) and Polish. Without a provider everything renders
in English — a missing provider is never an error. KptI18nProvider sets the language for a subtree:
import { KptI18nProvider, useKptI18n } from '@konce-pt/react';
export function App() {
return (
<KptI18nProvider locale="pl">
<Toolbar />
</KptI18nProvider>
);
}
function Toolbar() {
const { t, setLocale } = useKptI18n();
return (
<button onClick={() => setLocale('en')}>
{t('paginator.range', { start: 1, end: 10, total: 42 })}
</button>
);
}useKptI18n() returns { locale, setLocale, messages, messagesFor, t }. t(key, params?) takes a
dotted key with {name} interpolation and falls back to English, then to the key itself.
Add any language with one JSON (shape = the KptMessages contract; missing keys fall back to
English):
import de from './i18n/de.json';
<KptI18nProvider locale="de" messages={{ de }}>…</KptI18nProvider>Any component with labels also accepts locale and dictionary props, which take priority over the
provider. Calendar weekday/month names come from the browser's Intl API. The contract and the
dictionaries live in @konce-pt/i18n, shared with
the Angular port.
Helpers
cn(...values)— class-name joining (theclsxrole, no dependency).Slot+ theasChildprop — render as another element while keeping classes and attributes.composeRefs(...refs)— merge several refs into one callback.
Documentation
- Playground — live demos with copyable code: ui.konce.pt/react.
llms.txt— LLM-friendly API sheets per component, shipped inside the package; the package-wide sheet lives at ui.konce.pt/llms/react/llms.txt (PL mirror: llms-pl.txt).- Security scanners — Socket.dev and similar tools flag i18n keys as domain names
(
.email,.todayand friends are real TLDs) and the map's default endpoints; the project sheet explains each alert under "Security scanners". - Repository: gitlab.com/konce-pt/koncept-ui
License
MIT © konce.pt
🇵🇱 Wersja polska
Koncept UI to darmowy system projektowy na licencji MIT. Ta paczka to jego port na Reacta 19 — nie podobnie wyglądający zamiennik, tylko ten sam kontrakt renderowania.
- 🎯 Jedna warstwa CSS dla obu portów — te same klasy
kpt-*, te same tokeny--kpt-*, ta sama kolejność@layer kpt.*. Drzewo DOM odpowiada wersji Angulara węzeł w węzeł. - 📦 Zero zależności runtime —
cn,SloticomposeRefssą w paczce. - ⚛️ React 19 —
refjako zwykły prop (bezforwardRef), kontrolki działają kontrolowane i niekontrolowane, z natywnymname/form(React 19 Actions i React Hook Form bez adaptera). - 📊 Flagowa tabela danych — sortowanie, filtry globalne i kolumnowe, paginacja, virtual scroll, zaznaczanie, przestawianie i zamrażanie kolumn, edycja w miejscu, podsumowanie, eksport CSV.
- 🎨 100% na tokenach — każdy styl to
var(--kpt-*). Motyw jasny/ciemny, neutralna paleta OKLCH. - ♿ Natywne elementy —
<button>,<input>,<label>wszędzie tam, gdzie istnieją; stan błędu naaria-invalid, nie na klasie. - 🌍 Wbudowane i18n — angielski (domyślny) i polski, przełączanie w runtime, dowolny język jednym plikiem JSON.
Instalacja
npm i @konce-pt/react @konce-pt/tokens @konce-pt/stylesreact i react-dom (>=19) to peery, które masz już w aplikacji. Opcjonalnie:
@konce-pt/grid (układ; komponenty w @konce-pt/react/grid), @konce-pt/validators
(walidatory PL: NIP, REGON, PESEL, IBAN, kod pocztowy) oraz leaflet (^1.9) — opcjonalny
peer, potrzebny wyłącznie dla @konce-pt/react/map.
Konfiguracja
Paczka wysyła sam JavaScript. CSS ładujesz raz, w wejściu aplikacji:
import '@konce-pt/tokens/css';
import '@konce-pt/tokens/css/dark';
import '@konce-pt/styles'; // reset, kolejność warstw, motyw
import '@konce-pt/styles/components'; // warstwa komponentów — WYMAGANE, to ona styluje kpt-*
import '@konce-pt/grid'; // opcjonalnie — klasy układu@konce-pt/styles/components powstaje z tego samego SCSS-a, który port Angulara kompiluje do
własnego bundla — stąd identyczny wygląd obu portów. Angular wstrzykuje te style w runtime, React
nie ma dla nich innego źródła, więc ten import nie jest opcjonalny.
Motyw ciemny: <html data-theme="dark"> (albo prefers-color-scheme). Nadpisania --kpt-color-*
w arkuszu importowanym po @konce-pt/styles.
CDK nie jest tu potrzebny. Komponenty z panelem (
KptSelect,KptPopover,KptMenu,KptAutocomplete,KptDatepicker…) renderują go przez wewnętrznyKptOverlaydo portalu nadocument.body, więc@angular/cdk/overlay-prebuilt.cssnie jest wymagany. W otwartymKptDialogpanel trafia do<dialog>, boshowModal()przenosi go do górnej warstwy przeglądarki.
Ikony
84 ikony są wbudowane i nie wymagają konfiguracji — to, co rysują same komponenty, plus staple
powłoki aplikacji (sun, moon, bell, settings, users, logout, layout-dashboard,
home, activity, chart-bar, brand-github…). Typowy panel administracyjny nie potrzebuje nic
więcej.
Pełny zestaw Tabler Icons (MIT, 5130 ikon) ma dwie ścieżki, a różnica jest w bundlu. Każda ikona jest osobnym eksportem, więc import po nazwie zostawia w bundlu wyłącznie użyte:
// Produkcja — do bundla wchodzą dwie ikony.
import { LayoutDashboard, Rocket } from '@konce-pt/react/icons';
import { registerKptIcons } from '@konce-pt/react';
registerKptIcons([LayoutDashboard, Rocket]);
// Prototyp — wszystkie ikony, około 1,2 MB.
import { registerKptTablerIcons } from '@konce-pt/react/icons';
registerKptTablerIcons();To registerKptIcons odchudza, bo przyjmuje dane argumentem i sam nie importuje pełnego zestawu.
Z tego samego powodu registerKptIcons(tabler) z importem przestrzeni nazw (import * as tabler)
rejestruje komplet — namespace przekazany do funkcji blokuje analizę statyczną. Rejestr jest
globalny, więc wystarczy jedno wywołanie w wejściu aplikacji.
Internacjonalizacja (i18n)
Etykiety są dostępne po angielsku (domyślnie) i polsku. Bez providera wszystko renderuje się
po angielsku — brak providera nigdy nie jest błędem. <KptI18nProvider locale="pl"> ustawia język
dla poddrzewa, useKptI18n() zwraca { locale, setLocale, messages, messagesFor, t }, a
t('paginator.range', { start: 1, end: 10, total: 42 }) interpoluje {nazwa} i spada na EN, potem
na sam klucz.
Własny język jednym plikiem JSON (kształt = kontrakt KptMessages; brakujące klucze spadają
na EN):
import de from './i18n/de.json';
<KptI18nProvider locale="de" messages={{ de }}>…</KptI18nProvider>Propsy locale i dictionary na komponencie mają priorytet nad providerem. Nazwy dni i miesięcy
w kalendarzach pochodzą z Intl. Kontrakt i słowniki żyją w @konce-pt/i18n, wspólnym z portem
Angulara.
Komponenty (80+)
Formularze, przyciski i akcje, layout, nawigacja, dane (z flagową KptDataTable), feedback
i overlay — pełna lista w tabeli powyżej. Ciężkie rzeczy (wykresy, czat, roadmapa, mapy, pełny Tabler)
siedzą w subpathach @konce-pt/react/{charts,chat,roadmap,grid,map,icons}, żeby nie obciążać rdzenia.
Każdy komponent ma obok źródła plik llms.txt z opisem API.
Dokumentacja
Playground z demami: ui.konce.pt/react. Opisy API dla modeli:
llms.txt (EN) i
llms-pl.txt (PL). Repozytorium:
gitlab.com/konce-pt/koncept-ui.
Alerty skanerów łańcucha dostaw (Socket.dev i podobne) — klucze i18n czytane jak domeny oraz domyślne endpointy mapy — omawia sekcja „Skanery bezpieczeństwa" w arkuszu projektu.
Licencja
MIT © konce.pt
