@hypercerts-org/ui-svelte
v0.2.0
Published
Token-based Svelte component library for Hypercerts. Brand-blind primitives; a theme supplies the values.
Readme
@hypercerts-org/ui-svelte
Token-based Svelte component library. The primitives are brand-blind: they reference a closed set of semantic utilities and nothing else, and a theme decides what those mean. Re-branding is a token file, not a fork.
Three themes ship: Hypercerts (the default), Certified, and a template for a white-label partner.
Installing it
npm install @hypercerts-org/ui-svelte0.2.0 changes the API — Popover split into Popover and Menu, three
components renamed their accessible-name prop, and four were removed. Pre-1.0
the minor is the breaking position, so a ^0.1.0 range will not pull it. The
React package's README carries the full list.
/* your app's stylesheet — or a plain <link>, no build step required */
@import "@hypercerts-org/ui-svelte/styles.css";Tailwind is not needed. The utilities are compiled into that file and the
brand tokens are resolved into it at build time. It defines nothing outside the
components it renders — no reset, no body rule, no restyled headings.
If you want the document styled to match, that is a second, opt-in import:
@import "@hypercerts-org/ui-svelte/base.css"; /* optional */Pick a theme with one attribute on the root element:
<html data-hc-theme="hypercerts"><script>
import { Card, Heading, Eyebrow, Badge, Button } from "@hypercerts-org/ui-svelte";
</script>
<Card>
{#snippet children()}
<Eyebrow>{#snippet children()}Round 21{/snippet}</Eyebrow>
<Heading level={3} size="heading" turn="and who verified it.">
{#snippet children()}Who did the work{/snippet}
</Heading>
<Badge variant="success">{#snippet children()}Verified{/snippet}</Badge>
{/snippet}
</Card>Fonts are not included and cannot be. Switzer's licence prohibits
redistribution (see assets/fonts/README.md in this repository), so the package
ships font stacks, never files. Self-host Instrument Serif and Switzer
yourself — both are free — and the components pick them up.
Its relationship to the React package
This is not a reimplementation. Three things are shared source, compiled into both packages at build time, so the two cannot drift:
| shared | why it matters |
|---|---|
| packages/shared/styles + themes | one set of token values, so a brand change reaches both |
| packages/shared/classes | the class strings are data — a Svelte Button cannot look different from a React Button |
| packages/shared/dom | focus trap, scroll lock, dismissal. The most delicate code in the library, written once |
What differs is markup and lifecycle, which genuinely must.
The conformance suite (src/lib/conformance.test.ts) asserts the same
behaviour the React package asserts, written against roles and accessible names
rather than framework internals — "arrow keys skip a disabled tab", "Tab wraps
from last to first". When a behaviour changes it changes in both suites, or the
packages have diverged and CI says so.
What's here
Everything the React package has. The two are at parity, and
src/lib/conformance.test.ts and src/lib/intake.conformance.test.ts are what
hold them there — the same expectations, written against roles and accessible
names, asserted in both packages.
Foundations — Heading (with the italic turn), Eyebrow, Text, Prose.
Primitives — Button, Badge, Card, Avatar, Skeleton,
LoadingSpinner, Tooltip, InfoTooltip, ErrorMessage, EmptyState,
Separator, Progress.
Forms — Field, Input, Textarea, Select, Checkbox, Radio,
Switch, NumberInput, SearchInput, InputGroup (+ InputGroupAddon,
InputGroupInput), Slider, CharacterCount, FileDropzone, Calendar,
DatePicker.
Navigation — Tabs (with transition for the direction-aware panel
slide), SegmentedControl, Pagination, Breadcrumb, Stepper,
MenuButton, LoadMoreSentinel.
Overlays — Dialog, ConfirmByTypingDialog, ResponsiveDialog, Drawer,
BottomSheet, Menu + MenuItem, Popover, Banner, Toast.
Menu and Popover are different patterns and the difference matters.
Menu is a list of actions under role="menu" — one tab stop, arrow keys,
typeahead. Popover is a panel of arbitrary content under role="dialog", with
nothing intercepted, so a form inside it actually works.
Layout and data — Container, ScrollArea, Carousel + CarouselItem,
Reveal, Accordion, ExpandableText, Combobox + ComboboxOption,
LoadMoreSentinel, SettingRow, IdentityRow, OwnerByline, CopyableId,
ThemeToggle, and the Table family (Table, TableHeader, TableBody,
TableFooter, TableRow, TableHead, TableCell).
Where the APIs differ. Composite components take data here and compose
children in React — Tabs takes a tabs array, Accordion an items array,
Breadcrumb an items array. That is a real difference between the two
frameworks' idioms, not drift, and the conformance suite tests behaviour rather
than shape for exactly that reason.
Twenty of these came from the Ma Earth intake; see
../react/docs/maearth-intake.md.
The gallery
Svelte/Components has one entry per component — the sidebar is a component
list, not a page with fifty things on it. Svelte/Showcase stays grouped on
purpose: it is one composition that reads like a real page, which is a
different job from showing a component on its own.
One wrapper file rather than fifty, because this package renders through
fixtures either way. The React package splits the equivalent into
Primitives/…, Forms/…, Layout/… files and arrives at the same sidebar.
Scripts
| | |
|---|---|
| npm run storybook | the gallery, with a live theme switcher (port 6007) |
| npm run build | the package — dist/ plus the stylesheet |
| npm test | the conformance suite |
| npm run typecheck | svelte-check |
Publishing
npm version patch
git push --follow-tagsA ui-svelte-v* tag runs .github/workflows/release.yml, which checks the tag
matches package.json and publishes. prepublishOnly runs lint, typecheck,
tests and build first.
Not with npm provenance: that needs a public source repository, and this one is private for font-licensing reasons. See the note in the React package README.
