Components

Every component the crate defines, with the call that renders it in Yew, React and Svelte — followed by the design sections and what it takes to consume the crate.

01
eona-x.eu

Buttons

Three treatments, all fully-rounded pills: a solid brand-blue call to action, and two "glass" outlines for placing a button over a dark or a light surface.

rust
<PillButton label="Solid" kind={PillButtonKind::Solid} />
<PillButton label="Glass (on dark)" kind={PillButtonKind::GlassLight} />
<PillButton label="Glass (on light)" kind={PillButtonKind::GlassDark} />
PillButton props
PropTypeDefaultNotes
labelAttrValuerequiredButton text.
kindPillButtonKindSolidSolid, GlassLight (over dark), or GlassDark (over light).
hrefOption<AttrValue>NoneRenders an <a> when set, a <button> otherwise.
disabledboolfalseDisabled <button>; ignored when href is set.
02
eona-x.eu

Tags

A small dot-and-label pill used to categorize news and case-study cards.

rust
<TagLabel text="Actualité" />
<TagLabel text="Transport" tone={TagLabelTone::Blue} />
03
eona-x.eu

Form fields

Floating-label inputs, the shape used by the site's Gravity Forms contact form: a small label sits inside the field's own top padding rather than above it.

rust
<TextField id="gallery-name" label="Name" placeholder="Ada Lovelace" />
<TextField
    id="gallery-message"
    label="Message"
    kind={TextFieldKind::Textarea}
    placeholder="Tell us about your project…"
/>
TextField props
PropTypeDefaultNotes
idAttrValuerequiredTies the <label> to its control; must be unique on the page.
labelAttrValuerequiredFloating label text.
kindTextFieldKindInputInput or Textarea.
placeholderAttrValue""Shown until the field has a value.
04
eona-x.eu

Cards

News cards, case-study cards (a darker variant for a more editorial feel), and small frosted-glass member/partner logo tiles.

rust
<NewsCard
    href="#"
    tag="Actualité"
    title="Position Paper — EONA-X on Article 12 of the Data Governance Act"
/>
<CaseCard
    href="#"
    tag="Transport"
    title="Coordinating Paris 2024 without disrupting everyday travel"
/>

Member tiles

The homepage scrolls this row continuously via requestAnimationFrame — its CSS (".marquee"/".marquee-wrapper") has no keyframes of its own, the motion is all JS. Shown here as a static row instead of re-deriving that animation loop.

rust
<MemberCard name="AllTheWay" />
<MemberCard name="AnySolution" />
<MemberCard name="Partner Co." />
06
eona-x.eu

Accordion

Built on native <details>/<summary> — the only one of these four with no JS at all; the browser handles open/close on its own.

Mobility

EONA-X facilitates the sharing of data between public and private operators to streamline journeys, anticipate travel needs and offer more efficient multimodal mobility services.

Logistics

Connecting freight, port, and carrier data to reduce empty runs and make multimodal logistics chains easier to plan and track.

Tourism

Bringing together accommodation, transport, and destination data so trips across operators feel like one connected journey.

rust
<AccordionItem title="Mobility" open={true}>
    <p>{ "EONA-X facilitates the sharing of data between public and private operators…" }</p>
</AccordionItem>
<AccordionItem title="Logistics">
    <p>{ "Connecting freight, port, and carrier data…" }</p>
</AccordionItem>
08
eona-x.eu

Member directory card

A richer member card than the homepage's logo tile above, from the members-and-users directory page: logo, name, member type, country, and sector tags. The directory's actual filter form (country/sector dropdowns, results refreshed server-side) isn't ported — there's no backend here to filter against — only the card shape.

rust
<MemberFilterCard
    name="Carbookr"
    member_type="SME"
    country="France"
    sectors={vec!["Mobility"]}
/>
MemberFilterCard props
PropTypeDefaultNotes
nameAttrValuerequiredMember name; also the alt text of the logo tile.
member_typeAttrValuerequiredFree text, e.g. "SME" or "Large group".
countryAttrValuerequiredCountry label, rendered beside the type.
sectorsVec<&'static str>requiredOne tag per sector; an empty vec renders no tag row.
09
data quality

Charts

Pure SVG and no script, so these render server-side and print. The geometry is computed by chart::donut_arc and handed over already-derived — the components themselves do no arithmetic, which is what keeps them in the React package instead of its quarantine list.

rust
let score = donut_arc(0.842);
<ScoreDonut dash_array={score.dash_array} band={score.band}
            label={score.label} caption="Completeness" />
rust
<MetricTile label="Records analysed" value="128 430"
            help="Across 12 datasets">
    <EvolutionBadge value="+3.2 pts" trend={trend_of(3.2)} />
</MetricTile>
10
three stacks

Every component, in every stack

All 45 components the crate defines, one canonical call each: the Yew html!, then the 41 that also ship in @eona-x/ui-toolkit-react and @eona-x/ui-toolkit-svelte. Every pane is generated by cargo xtask bridgegen from abi/toolkit.abi.json — the same document that types both npm packages — so a prop added in src/ reaches all three at once, and none can be edited here without cargo xtask check reporting it.

The React and Svelte calls also run: every one rendered by React and every one rendered by Svelte, from the same generated list, styled by @eona-x/ui-toolkit-css.

The remaining 4 carry a note instead of JSX, because a React or Svelte snippet for a component index.ts does not export is an import that does not resolve. Section 18 argues the rule the notes quote; jump to it for the five that could have been generated and were not.

AccordionItem

src/molecules/accordion_item.rs:17

Yew
rust
use eona_ui_toolkit::AccordionItem;

<AccordionItem open={true} title="Title">
    { "Children go here." }
</AccordionItem>
React
tsx
import { AccordionItem } from '@eona-x/ui-toolkit-react';

<AccordionItem open title="Title">
  Children go here.
</AccordionItem>
Svelte
svelte
<script>
  import { AccordionItem } from '@eona-x/ui-toolkit-svelte';
</script>

<AccordionItem open title="Title">
  Children go here.
</AccordionItem>

BadgeNum

src/atoms/badge_num.rs:11

Yew
rust
use eona_ui_toolkit::BadgeNum;

<BadgeNum number="01" />
React
tsx
import { BadgeNum } from '@eona-x/ui-toolkit-react';

<BadgeNum number="01" />
Svelte
svelte
<script>
  import { BadgeNum } from '@eona-x/ui-toolkit-svelte';
</script>

<BadgeNum number="01" />

BgCheckItem

src/molecules/bg_check_item.rs:16

Yew
rust
use eona_ui_toolkit::BgCheckItem;

<BgCheckItem
    background="#fff"
    border="1px solid rgba(4,5,83,.12)"
    color="var(--eona-navy)"
    icon="✓"
    label="Label"
/>
React
tsx
import { BgCheckItem } from '@eona-x/ui-toolkit-react';

<BgCheckItem
  background="#fff"
  border="1px solid rgba(4,5,83,.12)"
  color="var(--eona-navy)"
  icon="✓"
  label="Label"
/>
Svelte
svelte
<script>
  import { BgCheckItem } from '@eona-x/ui-toolkit-svelte';
</script>

<BgCheckItem
  background="#fff"
  border="1px solid rgba(4,5,83,.12)"
  color="var(--eona-navy)"
  icon="✓"
  label="Label"
/>

Button

src/atoms/button.rs:30

Yew
rust
use eona_ui_toolkit::{Button, ButtonVariant};

<Button href="#" label="Label" variant={ButtonVariant::Ghost} />
React
tsx
import { Button } from '@eona-x/ui-toolkit-react';

<Button href="#" label="Label" variant="ghost" />
Svelte
svelte
<script>
  import { Button } from '@eona-x/ui-toolkit-svelte';
</script>

<Button href="#" label="Label" variant="ghost" />

Callout

src/molecules/callout.rs:13

Yew
rust
use eona_ui_toolkit::Callout;

<Callout english="English" french="French" label="Label" />
React
tsx
import { Callout } from '@eona-x/ui-toolkit-react';

<Callout english="English" french="French" label="Label" />
Svelte
svelte
<script>
  import { Callout } from '@eona-x/ui-toolkit-svelte';
</script>

<Callout english="English" french="French" label="Label" />

CaseCard

src/molecules/case_card.rs:15

Yew
rust
use eona_ui_toolkit::CaseCard;

<CaseCard href="#" tag="Tag" title="Title" />
React
tsx
import { CaseCard } from '@eona-x/ui-toolkit-react';

<CaseCard href="#" tag="Tag" title="Title" />
Svelte
svelte
<script>
  import { CaseCard } from '@eona-x/ui-toolkit-svelte';
</script>

<CaseCard href="#" tag="Tag" title="Title" />

CodeBlock

src/atoms/code_block.rs:27

Yew
rust
use eona_ui_toolkit::CodeBlock;

<CodeBlock code={r#"let label = "Go";"#} copyable={false} language="rust" />
React
tsx
import { CodeBlock } from '@eona-x/ui-toolkit-react';

<CodeBlock code={"let label = \"Go\";"} copyable={false} language="rust" />
Svelte
svelte
<script>
  import { CodeBlock } from '@eona-x/ui-toolkit-svelte';
</script>

<CodeBlock code={"let label = \"Go\";"} copyable={false} language="rust" />

ComponentsGallery

src/organisms/components_gallery.rs:28

Yew
rust
use eona_ui_toolkit::ComponentsGallery;

<ComponentsGallery />
React & Svelte — not shipped

never generated — out of scope by rule: zero-prop demo section

DatasetCard

src/molecules/dataset_card.rs:57

Yew
rust
use eona_ui_toolkit::DatasetCard;

<DatasetCard
    action_label="View documentation"
    badge="Badge"
    badge_class="Badge Class"
    description="Description"
    href="#"
    initial="Initial"
    publisher="Publisher"
    thumbnail="logo/logo-transparent.svg"
    title="Title"
    version="Version"
/>
React
tsx
import { DatasetCard } from '@eona-x/ui-toolkit-react';

<DatasetCard
  actionLabel="View documentation"
  badge="Badge"
  badgeClass="Badge Class"
  description="Description"
  href="#"
  initial="Initial"
  publisher="Publisher"
  thumbnail="logo/logo-transparent.svg"
  title="Title"
  version="Version"
/>
Svelte
svelte
<script>
  import { DatasetCard } from '@eona-x/ui-toolkit-svelte';
</script>

<DatasetCard
  actionLabel="View documentation"
  badge="Badge"
  badgeClass="Badge Class"
  description="Description"
  href="#"
  initial="Initial"
  publisher="Publisher"
  thumbnail="logo/logo-transparent.svg"
  title="Title"
  version="Version"
/>

DeclCard

src/molecules/decl_card.rs:17

Yew
rust
use eona_ui_toolkit::DeclCard;

<DeclCard
    alt="EONA-X logo"
    category="Category"
    decl_type="Decl Type"
    description="Description"
    src="logo/logo-transparent.svg"
    title="Title"
/>
React
tsx
import { DeclCard } from '@eona-x/ui-toolkit-react';

<DeclCard
  alt="EONA-X logo"
  category="Category"
  declType="Decl Type"
  description="Description"
  src="logo/logo-transparent.svg"
  title="Title"
/>
Svelte
svelte
<script>
  import { DeclCard } from '@eona-x/ui-toolkit-svelte';
</script>

<DeclCard
  alt="EONA-X logo"
  category="Category"
  declType="Decl Type"
  description="Description"
  src="logo/logo-transparent.svg"
  title="Title"
/>

DemoPage

src/demo.rs:62

Yew
rust
use eona_ui_toolkit::{NavLink, SiteNavLink};
use eona_ui_toolkit::demo::DemoPage;

<DemoPage
    footer_link={NavLink { href: "#".into(), label: "Label".into() }}
    footer_text="Footer Text"
    nav={vec![SiteNavLink { href: "#".into(), label: "Label".into(), current: false }]}
/>
React & Svelte — not shipped

never generated — out of scope by rule: crate showcase page

EvolutionBadge

src/atoms/evolution_badge.rs:23

Yew
rust
use eona_ui_toolkit::EvolutionBadge;
use eona_ui_toolkit::chart::Trend;

<EvolutionBadge trend={Trend::Up} value="An annotation value" />
React
tsx
import { EvolutionBadge } from '@eona-x/ui-toolkit-react';

<EvolutionBadge trend="up" value="An annotation value" />
Svelte
svelte
<script>
  import { EvolutionBadge } from '@eona-x/ui-toolkit-svelte';
</script>

<EvolutionBadge trend="up" value="An annotation value" />

Hero

src/organisms/hero.rs:35

Yew
rust
use eona_ui_toolkit::{Hero, HeroVariant};

<Hero
    eyebrow="Eyebrow"
    lede="Lede"
    title_suffix="Title Suffix"
    variant={HeroVariant::Full}
>
    { "Children go here." }
</Hero>
React
tsx
import { Hero } from '@eona-x/ui-toolkit-react';

<Hero
  eyebrow="Eyebrow"
  lede="Lede"
  titleSuffix="Title Suffix"
  variant="full"
>
  Children go here.
</Hero>
Svelte
svelte
<script>
  import { Hero } from '@eona-x/ui-toolkit-svelte';
</script>

<Hero
  eyebrow="Eyebrow"
  lede="Lede"
  titleSuffix="Title Suffix"
  variant="full"
>
  Children go here.
</Hero>

HeroFilter

src/organisms/hero_filter.rs:34

Yew
rust
use eona_ui_toolkit::HeroFilter;

<HeroFilter description="Description" title="Title" with_backdrop={true}>
    { "Children go here." }
</HeroFilter>
React
tsx
import { HeroFilter } from '@eona-x/ui-toolkit-react';

<HeroFilter description="Description" title="Title" withBackdrop>
  Children go here.
</HeroFilter>
Svelte
svelte
<script>
  import { HeroFilter } from '@eona-x/ui-toolkit-svelte';
</script>

<HeroFilter description="Description" title="Title" withBackdrop>
  Children go here.
</HeroFilter>

IntegrationSection

src/organisms/integration_section.rs:33

Yew
rust
use eona_ui_toolkit::IntegrationSection;

<IntegrationSection />
React & Svelte — not shipped

never generated — out of scope by rule: zero-prop demo section

LogoDownloadCard

src/molecules/logo_download_card.rs:22

Yew
rust
use eona_ui_toolkit::LogoDownloadCard;

<LogoDownloadCard
    download_name="Download Name"
    file="logo/logo-transparent.svg"
    is_transparent={true}
    label="Label"
    preview_style="background:var(--eona-navy);padding:24px;"
/>
React
tsx
import { LogoDownloadCard } from '@eona-x/ui-toolkit-react';

<LogoDownloadCard
  downloadName="Download Name"
  file="logo/logo-transparent.svg"
  isTransparent
  label="Label"
  previewStyle="background:var(--eona-navy);padding:24px;"
/>
Svelte
svelte
<script>
  import { LogoDownloadCard } from '@eona-x/ui-toolkit-svelte';
</script>

<LogoDownloadCard
  downloadName="Download Name"
  file="logo/logo-transparent.svg"
  isTransparent
  label="Label"
  previewStyle="background:var(--eona-navy);padding:24px;"
/>

MainNav

src/organisms/main_nav.rs:26

Yew
rust
use eona_ui_toolkit::{MainNav, NavLink};

<MainNav
    back={NavLink { href: "#".into(), label: "Label".into() }}
    links={vec![NavLink { href: "#".into(), label: "Label".into() }]}
/>
React
tsx
import { MainNav } from '@eona-x/ui-toolkit-react';

<MainNav
  back={{ href: "#", label: "Label" }}
  links={[{ href: "#", label: "Label" }]}
/>
Svelte
svelte
<script>
  import { MainNav } from '@eona-x/ui-toolkit-svelte';
</script>

<MainNav
  back={{ href: "#", label: "Label" }}
  links={[{ href: "#", label: "Label" }]}
/>

MemberCard

src/molecules/member_card.rs:11

Yew
rust
use eona_ui_toolkit::MemberCard;

<MemberCard name="Name" />
React
tsx
import { MemberCard } from '@eona-x/ui-toolkit-react';

<MemberCard name="Name" />
Svelte
svelte
<script>
  import { MemberCard } from '@eona-x/ui-toolkit-svelte';
</script>

<MemberCard name="Name" />

MemberFilterCard

src/molecules/member_filter_card.rs:18

Yew
rust
use eona_ui_toolkit::MemberFilterCard;

<MemberFilterCard
    country="Country"
    member_type="Member Type"
    name="Name"
    sectors={vec!["Mobility"]}
/>
React
tsx
import { MemberFilterCard } from '@eona-x/ui-toolkit-react';

<MemberFilterCard
  country="Country"
  memberType="Member Type"
  name="Name"
  sectors={["Mobility"]}
/>
Svelte
svelte
<script>
  import { MemberFilterCard } from '@eona-x/ui-toolkit-svelte';
</script>

<MemberFilterCard
  country="Country"
  memberType="Member Type"
  name="Name"
  sectors={["Mobility"]}
/>

MetricTile

src/molecules/metric_tile.rs:28

Yew
rust
use eona_ui_toolkit::MetricTile;

<MetricTile help="Help" label="Label" value="An annotation value">
    { "Children go here." }
</MetricTile>
React
tsx
import { MetricTile } from '@eona-x/ui-toolkit-react';

<MetricTile help="Help" label="Label" value="An annotation value">
  Children go here.
</MetricTile>
Svelte
svelte
<script>
  import { MetricTile } from '@eona-x/ui-toolkit-svelte';
</script>

<MetricTile help="Help" label="Label" value="An annotation value">
  Children go here.
</MetricTile>

Modal

src/molecules/modal.rs:28

Yew
rust
use eona_ui_toolkit::Modal;

<Modal id="example" open={true} title="Title">
    { "Children go here." }
</Modal>
React
tsx
import { Modal } from '@eona-x/ui-toolkit-react';

<Modal id="example" open title="Title">
  Children go here.
</Modal>
Svelte
svelte
<script>
  import { Modal } from '@eona-x/ui-toolkit-svelte';
</script>

<Modal id="example" open title="Title">
  Children go here.
</Modal>

MvoCard

src/molecules/mvo_card.rs:11

Yew
rust
use eona_ui_toolkit::MvoCard;

<MvoCard description="Description" tag="Tag" />
React
tsx
import { MvoCard } from '@eona-x/ui-toolkit-react';

<MvoCard description="Description" tag="Tag" />
Svelte
svelte
<script>
  import { MvoCard } from '@eona-x/ui-toolkit-svelte';
</script>

<MvoCard description="Description" tag="Tag" />

NavDropdown

src/molecules/nav_dropdown.rs:22

Yew
rust
use eona_ui_toolkit::NavDropdown;

<NavDropdown id="example" label="Label" links={vec![("#", "About")]} />
React
tsx
import { NavDropdown } from '@eona-x/ui-toolkit-react';

<NavDropdown id="example" label="Label" links={[["#", "About"]]} />
Svelte
svelte
<script>
  import { NavDropdown } from '@eona-x/ui-toolkit-svelte';
</script>

<NavDropdown id="example" label="Label" links={[["#", "About"]]} />

NewsCard

src/molecules/news_card.rs:16

Yew
rust
use eona_ui_toolkit::NewsCard;

<NewsCard href="#" tag="Tag" title="Title" />
React
tsx
import { NewsCard } from '@eona-x/ui-toolkit-react';

<NewsCard href="#" tag="Tag" title="Title" />
Svelte
svelte
<script>
  import { NewsCard } from '@eona-x/ui-toolkit-svelte';
</script>

<NewsCard href="#" tag="Tag" title="Title" />

OntoAnnotation

src/molecules/annotation.rs:38

Yew
rust
use eona_ui_toolkit::OntoAnnotation;
use eona_ui_toolkit::ontology::LiteralValue;

<OntoAnnotation
    label="Label"
    values={vec![LiteralValue { value: "An annotation value".into(), language: None, datatype: None, datatype_label: None }]}
/>
React
tsx
import { OntoAnnotation } from '@eona-x/ui-toolkit-react';

<OntoAnnotation label="Label" values={[{ value: "An annotation value" }]} />
Svelte
svelte
<script>
  import { OntoAnnotation } from '@eona-x/ui-toolkit-svelte';
</script>

<OntoAnnotation label="Label" values={[{ value: "An annotation value" }]} />

OntoBadge

src/atoms/onto_badge.rs:66

Yew
rust
use eona_ui_toolkit::{BadgeVariant, OntoBadge};

<OntoBadge label="Label" title="Title" variant={BadgeVariant::Lang} />
React
tsx
import { OntoBadge } from '@eona-x/ui-toolkit-react';

<OntoBadge label="Label" title="Title" variant="lang" />
Svelte
svelte
<script>
  import { OntoBadge } from '@eona-x/ui-toolkit-svelte';
</script>

<OntoBadge label="Label" title="Title" variant="lang" />

OntologySection

src/organisms/ontology_section.rs:28

Yew
rust
use eona_ui_toolkit::OntologySection;

<OntologySection />
React & Svelte — not shipped

never generated — out of scope by rule: zero-prop demo section

PaletteGroup

src/molecules/palette_group.rs:43

Yew
rust
use eona_ui_toolkit::{ColorSpec, PaletteGroup};

<PaletteGroup
    colors={vec![ColorSpec { name: "Name".into(), hex: "#040553".into(), usage: "Primary brand colour".into(), ink: "Ink".into(), variants_label: None }]}
    description="Description"
    note="Note"
    title="Title"
/>
React
tsx
import { PaletteGroup } from '@eona-x/ui-toolkit-react';

<PaletteGroup
  colors={[{ name: "Name", hex: "#040553", usage: "Primary brand colour", ink: "Ink" }]}
  description="Description"
  note="Note"
  title="Title"
/>
Svelte
svelte
<script>
  import { PaletteGroup } from '@eona-x/ui-toolkit-svelte';
</script>

<PaletteGroup
  colors={[{ name: "Name", hex: "#040553", usage: "Primary brand colour", ink: "Ink" }]}
  description="Description"
  note="Note"
  title="Title"
/>

PersonalityCard

src/molecules/personality_card.rs:11

Yew
rust
use eona_ui_toolkit::PersonalityCard;

<PersonalityCard description="Description" tag="Tag" />
React
tsx
import { PersonalityCard } from '@eona-x/ui-toolkit-react';

<PersonalityCard description="Description" tag="Tag" />
Svelte
svelte
<script>
  import { PersonalityCard } from '@eona-x/ui-toolkit-svelte';
</script>

<PersonalityCard description="Description" tag="Tag" />

Pill

src/atoms/pill.rs:28

Yew
rust
use eona_ui_toolkit::{Pill, PillTone};

<Pill tone={PillTone::Bad}>
    { "Children go here." }
</Pill>
React
tsx
import { Pill } from '@eona-x/ui-toolkit-react';

<Pill tone="bad">
  Children go here.
</Pill>
Svelte
svelte
<script>
  import { Pill } from '@eona-x/ui-toolkit-svelte';
</script>

<Pill tone="bad">
  Children go here.
</Pill>

PillButton

src/atoms/pill_button.rs:37

Yew
rust
use eona_ui_toolkit::{PillButton, PillButtonKind};

<PillButton
    disabled={true}
    href="#"
    kind={PillButtonKind::GlassLight}
    label="Label"
/>
React
tsx
import { PillButton } from '@eona-x/ui-toolkit-react';

<PillButton disabled href="#" kind="glass-light" label="Label" />
Svelte
svelte
<script>
  import { PillButton } from '@eona-x/ui-toolkit-svelte';
</script>

<PillButton disabled href="#" kind="glass-light" label="Label" />

PillarCard

src/molecules/pillar_card.rs:13

Yew
rust
use eona_ui_toolkit::PillarCard;

<PillarCard
    num="01"
    points={vec![("Open", "by default, not by exception")]}
    title="Title"
/>
React
tsx
import { PillarCard } from '@eona-x/ui-toolkit-react';

<PillarCard
  num="01"
  points={[["Open", "by default, not by exception"]]}
  title="Title"
/>
Svelte
svelte
<script>
  import { PillarCard } from '@eona-x/ui-toolkit-svelte';
</script>

<PillarCard
  num="01"
  points={[["Open", "by default, not by exception"]]}
  title="Title"
/>

PropsTable

src/molecules/props_table.rs:40

Yew
rust
use eona_ui_toolkit::{PropSpec, PropsTable};

<PropsTable
    component="Button"
    props={vec![PropSpec { name: "Name".into(), ty: "AttrValue".into(), default: None, doc: "What the prop does.".into() }]}
/>
React
tsx
import { PropsTable } from '@eona-x/ui-toolkit-react';

<PropsTable
  component="Button"
  props={[{ name: "Name", ty: "AttrValue", doc: "What the prop does." }]}
/>
Svelte
svelte
<script>
  import { PropsTable } from '@eona-x/ui-toolkit-svelte';
</script>

<PropsTable
  component="Button"
  props={[{ name: "Name", ty: "AttrValue", doc: "What the prop does." }]}
/>

RefCard

src/molecules/ref_card.rs:14

Yew
rust
use eona_ui_toolkit::RefCard;

<RefCard
    alt="EONA-X logo"
    description="Description"
    src="logo/logo-transparent.svg"
    title="Title"
/>
React
tsx
import { RefCard } from '@eona-x/ui-toolkit-react';

<RefCard
  alt="EONA-X logo"
  description="Description"
  src="logo/logo-transparent.svg"
  title="Title"
/>
Svelte
svelte
<script>
  import { RefCard } from '@eona-x/ui-toolkit-svelte';
</script>

<RefCard
  alt="EONA-X logo"
  description="Description"
  src="logo/logo-transparent.svg"
  title="Title"
/>

RuleListItem

src/molecules/rule_list_item.rs:28

Yew
rust
use eona_ui_toolkit::{RuleKind, RuleListItem};

<RuleListItem kind={RuleKind::Do}>
    { "Children go here." }
</RuleListItem>
React
tsx
import { RuleListItem } from '@eona-x/ui-toolkit-react';

<RuleListItem kind="do">
  Children go here.
</RuleListItem>
Svelte
svelte
<script>
  import { RuleListItem } from '@eona-x/ui-toolkit-svelte';
</script>

<RuleListItem kind="do">
  Children go here.
</RuleListItem>

ScoreDonut

src/molecules/score_donut.rs:29

Yew
rust
use eona_ui_toolkit::ScoreDonut;
use eona_ui_toolkit::chart::ScoreBand;

<ScoreDonut
    band={ScoreBand::Good}
    caption="Caption"
    dash_array="Dash Array"
    label="Label"
/>
React
tsx
import { ScoreDonut } from '@eona-x/ui-toolkit-react';

<ScoreDonut
  band="good"
  caption="Caption"
  dashArray="Dash Array"
  label="Label"
/>
Svelte
svelte
<script>
  import { ScoreDonut } from '@eona-x/ui-toolkit-svelte';
</script>

<ScoreDonut
  band="good"
  caption="Caption"
  dashArray="Dash Array"
  label="Label"
/>

SectionHead

src/molecules/section_head.rs:15

Yew
rust
use eona_ui_toolkit::SectionHead;

<SectionHead kicker="Kicker" number="01" title="Title" />
React
tsx
import { SectionHead } from '@eona-x/ui-toolkit-react';

<SectionHead kicker="Kicker" number="01" title="Title" />
Svelte
svelte
<script>
  import { SectionHead } from '@eona-x/ui-toolkit-svelte';
</script>

<SectionHead kicker="Kicker" number="01" title="Title" />

ShapeCard

src/molecules/shape_card.rs:12

Yew
rust
use eona_ui_toolkit::ShapeCard;
use yew::prelude::*;

<ShapeCard
    demo={html! { <span>{ "Slot content." }</span> }}
    description="Description"
    title="Title"
/>
React
tsx
import { ShapeCard } from '@eona-x/ui-toolkit-react';

<ShapeCard
  demo={<span>Slot content.</span>}
  description="Description"
  title="Title"
/>
Svelte
svelte
<script>
  import { ShapeCard } from '@eona-x/ui-toolkit-svelte';
</script>

<ShapeCard description="Description" title="Title">
  {#snippet demo()}<span>Slot content.</span>{/snippet}
</ShapeCard>

SimpleFooter

src/organisms/simple_footer.rs:13

Yew
rust
use eona_ui_toolkit::{NavLink, SimpleFooter};

<SimpleFooter
    link={NavLink { href: "#".into(), label: "Label".into() }}
    text="Text"
/>
React
tsx
import { SimpleFooter } from '@eona-x/ui-toolkit-react';

<SimpleFooter link={{ href: "#", label: "Label" }} text="Text" />
Svelte
svelte
<script>
  import { SimpleFooter } from '@eona-x/ui-toolkit-svelte';
</script>

<SimpleFooter link={{ href: "#", label: "Label" }} text="Text" />

SiteHeader

src/organisms/site_header.rs:96

Yew
rust
use eona_ui_toolkit::{SiteHeader, SiteNavLink};

<SiteHeader
    links={vec![SiteNavLink { href: "#".into(), label: "Label".into(), current: false }]}
    show_login={true}
/>
React
tsx
import { SiteHeader } from '@eona-x/ui-toolkit-react';

<SiteHeader
  links={[{ href: "#", label: "Label", current: false }]}
  showLogin
/>
Svelte
svelte
<script>
  import { SiteHeader } from '@eona-x/ui-toolkit-svelte';
</script>

<SiteHeader
  links={[{ href: "#", label: "Label", current: false }]}
  showLogin
/>

Swatch

src/molecules/swatch.rs:44

Yew
rust
use eona_ui_toolkit::Swatch;

<Swatch
    hex="#040553"
    ink="Ink"
    name="Name"
    usage="Primary brand colour"
    variants="Variants"
/>
React
tsx
import { Swatch } from '@eona-x/ui-toolkit-react';

<Swatch
  hex="#040553"
  ink="Ink"
  name="Name"
  usage="Primary brand colour"
  variants="Variants"
/>
Svelte
svelte
<script>
  import { Swatch } from '@eona-x/ui-toolkit-svelte';
</script>

<Swatch
  hex="#040553"
  ink="Ink"
  name="Name"
  usage="Primary brand colour"
  variants="Variants"
/>

TagLabel

src/atoms/tag_label.rs:27

Yew
rust
use eona_ui_toolkit::{TagLabel, TagLabelTone};

<TagLabel text="Text" tone={TagLabelTone::Blue} />
React
tsx
import { TagLabel } from '@eona-x/ui-toolkit-react';

<TagLabel text="Text" tone="blue" />
Svelte
svelte
<script>
  import { TagLabel } from '@eona-x/ui-toolkit-svelte';
</script>

<TagLabel text="Text" tone="blue" />

TextField

src/molecules/text_field.rs:22

Yew
rust
use eona_ui_toolkit::{TextField, TextFieldKind};

<TextField
    id="example"
    kind={TextFieldKind::Textarea}
    label="Label"
    placeholder="Placeholder"
/>
React
tsx
import { TextField } from '@eona-x/ui-toolkit-react';

<TextField
  id="example"
  kind="textarea"
  label="Label"
  placeholder="Placeholder"
/>
Svelte
svelte
<script>
  import { TextField } from '@eona-x/ui-toolkit-svelte';
</script>

<TextField
  id="example"
  kind="textarea"
  label="Label"
  placeholder="Placeholder"
/>

TypeScaleRow

src/molecules/type_scale_row.rs:14

Yew
rust
use eona_ui_toolkit::TypeScaleRow;
use yew::prelude::*;

<TypeScaleRow
    role="Role"
    sample={html! { <span>{ "Slot content." }</span> }}
    spec="Spec"
/>
React
tsx
import { TypeScaleRow } from '@eona-x/ui-toolkit-react';

<TypeScaleRow role="Role" sample={<span>Slot content.</span>} spec="Spec" />
Svelte
svelte
<script>
  import { TypeScaleRow } from '@eona-x/ui-toolkit-svelte';
</script>

<TypeScaleRow role="Role" spec="Spec">
  {#snippet sample()}<span>Slot content.</span>{/snippet}
</TypeScaleRow>

UsageExample

src/molecules/usage_example.rs:24

Yew
rust
use eona_ui_toolkit::UsageExample;

<UsageExample on_dark={true} source={r#"<Button label="Go" />"#}>
    { "Children go here." }
</UsageExample>
React
tsx
import { UsageExample } from '@eona-x/ui-toolkit-react';

<UsageExample onDark source={"<Button label=\"Go\" />"}>
  Children go here.
</UsageExample>
Svelte
svelte
<script>
  import { UsageExample } from '@eona-x/ui-toolkit-svelte';
</script>

<UsageExample onDark source={"<Button label=\"Go\" />"}>
  Children go here.
</UsageExample>
10
integration

What the crate ships

Presentational components and the three stylesheets that make them look like EONA-X. Nothing else: no state, no data fetching, no page. They are consumed from two stacks — as a Rust crate, and as a React package generated from that same crate — so this guide is in two halves. Sections 11-14 are Yew; sections 15-19 are React. Neither half's instructions apply to the other stack, and each section's kicker says which one it belongs to. For the call itself — one canonical snippet per component, side by side in both stacks — see section 09, which is generated rather than written and so cannot fall behind src/.

The portal's page sections went home

The charter slides and the dataspace-architecture diagrams — ColorSection, DataspaceLayers, IdsRamLayers and the rest — are in developer.eona-x.eu under crates/site/src/sections/. They took no props and had exactly one caller, the portal itself.

BeeNest went with them

The bee-nest diagrams and the Turtle-to-Rust codegen behind them are the same repository's crates/site/build.rs and crates/site/data/*.ttl. Their content is EONA-X architecture rather than EONA-X brand, and it changes on the architecture's schedule, not the design system's.

The ontology browser went back to its app

The stateful term browser, the ontology model and the JSON-LD parser are in eona-vocabulary-ui, which wrote them and was their only consumer. The two presentational pieces stayed and are documented in sections 20-23: OntoBadge and OntoAnnotation, with the two model types they are typed on.

One consumer is not reuse.

Each of those three had precisely one caller, and every consumer of the toolkit was paying for all of them in compile time, dependency surface and API to read past. The crate's whole dependency list is now yew — the Turtle parsers left with BeeNest, serde left with the JSON-LD parser, and the five browser crates left with the stateful tier.

11
yew

Add the crate

The components are renderer-agnostic, so neither renderer feature is on by default — a consumer picks one. ssr renders to HTML strings at build time (what a static-site generator wants); csr mounts them in a browser, which is what a WASM host needs. Enabling neither compiles the components but leaves you no way to render them.

toml
# static site generation
eona-ui-toolkit = { path = "../eona-ui-toolkit", features = ["ssr"] }

# browser / WASM host
eona-ui-toolkit = { path = "../eona-ui-toolkit", features = ["csr"] }

The choice is now smaller than it looks: csr used to gate a whole module of stateful components as well, and since that module moved to eona-vocabulary-ui the feature selects Yew's renderer and nothing else. It is kept under the same name so existing manifests keep resolving, and because which renderer a consumer wants is still a question only the consumer can answer.

The toolkit targets Yew 0.23, the same release edc-web-components targets. That alignment is not cosmetic: components built against different Yew versions cannot share Html, props or contexts, so a version mismatch makes composing the two libraries in one binary impossible rather than merely awkward.

12
yew

Ship the stylesheets

Markup and class names are ported verbatim from the brand charter, so the stylesheets are the rendering contract, not a theme: without them the components render as unstyled HTML. They ship inside the crate as string constants so consumers never reach into its on-disk layout.

rust
use eona_ui_toolkit::{COMPONENTS_CSS, ONTOLOGY_CSS, TOKENS_CSS};

std::fs::write(dist.join("tokens.css"), TOKENS_CSS)?;
std::fs::write(dist.join("components.css"), COMPONENTS_CSS)?;
std::fs::write(dist.join("ontology.css"), ONTOLOGY_CSS)?;   // only if you render OntoBadge / OntoAnnotation

Link all three. The order below is the house convention and not a rendering requirement: tokens.css is the only one of the three that declares a custom property — components.css has no :root block at all — and a custom property is resolved per element at computed-value time rather than in stylesheet order, so swapping the links changes nothing a browser renders. What does break the page is leaving tokens.css out, because then every var() in the other two resolves to nothing.

html
<link rel="stylesheet" href="tokens.css">
<link rel="stylesheet" href="components.css">
<link rel="stylesheet" href="ontology.css">
Why three files and not two.

The badge and annotation rules are a separate constant because ontology.css is a name three repositories already write out by hand. The rest of that stylesheet — the browser's own classes — left with the stateful tier, so what remains is small; a host that renders neither of those two components can skip it.

13
yew

Render a page

This very page is a component. DemoPage is the whole document body — header, hero, gallery, and this section — and DEMO_SHELL is the surrounding HTML document with an __APP__ placeholder for it. A host renders one into the other:

rust
use eona_ui_toolkit::demo::{DemoPage, DEMO_JS, DEMO_SHELL};
use yew::ServerRenderer;

let body = ServerRenderer::<DemoPage>::new().render().await;
std::fs::write(dist.join("components.html"), DEMO_SHELL.replace("__APP__", &body))?;
std::fs::write(dist.join("demo-page.js"), DEMO_JS)?;
Six assets the shell expects, and one is not in the crate.

Five are crate constants, written out above and in section 12: tokens.css, components.css, ontology.css, demo-page.js and LOGO_SVG, the EONA-X mark — write that one to logo/logo-transparent.svg, the path SiteHeader hardcodes twice (src/organisms/site_header.rs:101,105) and the shell links as the favicon (assets/demo-shell.html:14). It ships here because a component that names an asset should carry it; hosts used to have to copy the file in and got a header with a hole in it when they forgot.

The one exception is site-header.js, the scroll-collapse glue for SiteHeader. It stays with the portal, whose every page uses it; omit it and the header renders and simply never collapses — degraded, not broken. It is worth knowing on the React side too: SiteHeader is one of the 33 exported components and the npm package carries no assets at all.

Every component in the toolkit is presentational: no use_state, no Callback, no event handlers. They render identically under SSR and CSR, and the interaction you can click on this page — the dropdowns, the modal, the copy buttons — is page glue in DEMO_JS operating on the rendered markup. That is also what makes the React package of sections 15-19 possible at all.

14
yew

Vendoring: pick by what you need

There are two ways in, and the right one depends on whether you want the components or the whole page.

A site that publishes this demo page

Use a git submodule plus a path dependency. That is what developer.eona-x.eu does, and it is a deployment constraint rather than a preference: its Dockerfile builds from COPY . ., so a submodule arrives in the image as ordinary files, while a git dependency on a private GitLab remote would need credentials inside the build. It also keeps the crate's working tree in the host's checkout, so the page and the components it documents move together.

sh
git submodule add \
  ssh://git@gitlab.eona-x.org:29418/eona-x/web/eona-ui-toolkit.git \
  crates/eona-ui-toolkit

# EXCLUDE it from the host workspace, then depend on it by path. In the host's
# own Cargo.toml:
#   [workspace]    exclude = ["crates/eona-ui-toolkit"]
# and in the crate that renders (crates/site/Cargo.toml, so the path is relative
# to THAT manifest, not to the workspace root):
#   [dependencies] eona-ui-toolkit = { path = "../eona-ui-toolkit", features = ["ssr"] }

git clone --recurse-submodules <host-repo>   # for a fresh checkout
git submodule update --init                  # for an existing one

The exclude is not optional. This crate became its own workspace root when it gained xtask, and a path dependency living inside the host's workspace directory is pulled in as a member by default, which is a hard error: multiple workspace roots found in the same workspace before anything compiles. The error names two directories and no file you edited, so it is worth recognising. developer.eona-x.eu carries exactly that line (Cargo.toml:10), and nothing is lost by it: the toolkit runs its own tests from its own repository.

A checkout that skips the submodule step gets an empty crates/eona-ui-toolkit/ and a Cargo error about a missing manifest — the usual first symptom.

An app that just needs the components

Use an ordinary git dependency. Nothing about a submodule helps here: an app that renders its own screens never touches DemoPage, DEMO_SHELL or DEMO_JS, so the demo page is weight it carries without using. Cargo resolves the dependency, Cargo.lock pins the exact revision, and there is no submodule to forget to initialise.

toml
[dependencies]
# No tag contains the crate this page describes. The remote has v0.2.0 and v0.3.0,
# both cut before the page sections, BeeNest and the ontology tier moved out, so
# pin a rev until a release is tagged off the slimmed history.
eona-ui-toolkit = { git = "ssh://git@gitlab.eona-x.org:29418/eona-x/web/eona-ui-toolkit.git", rev = "<commit>", features = ["csr"] }
Pin a tag or a rev, not a branch.

A branch dependency re-resolves whenever the lockfile is regenerated, which turns an unrelated cargo update into a design-system change nobody asked for. Check which refs exist before you pin one: git ls-remote --tags today returns only v0.2.0 and v0.3.0, and pinning a tag that was never cut fails at resolve time with a fetch error, which reads like a permissions problem and is not one.

The credentials objection that rules this out for the portal does not generally apply: an ordinary cargo build on a developer machine, or a CI job with an SSH key or deploy token, authenticates fine. It only bites when the build happens somewhere that cannot carry a credential — which is exactly the portal's Docker stage, and is why that one repository does it the other way.

15
react

Install the package

Not published yet.

Nothing below installs today. @eona-x/ui-toolkit-react is generated into this repository at packages/react, and packaging and publishing it is Layer 4 of eona-x/backlog#809 — not #808, which ships the stylesheet package and lists this one as out of scope. Watch #809 if you are waiting to install; #808 is its prerequisite and can close with nothing to install yet.

Two fallbacks work meanwhile, and one obvious one does not. Copy the generated packages/react/src/*.tsx into the app, or vendor this repository and run npm install && npm run build inside packages/react first. Pointing a dependency at that directory without building it resolves to nothing: the package's entry points are all under dist/, which is generated by tsup and is neither committed nor present in a fresh checkout.

The package is scoped to @eona-x and will live in the eona-x group's npm registry on gitlab.eona-x.org, not on npmjs.org. npm has no way to discover that, so a consumer declares it in .npmrc:

ini
# route only the @eona-x scope to GitLab; everything else still resolves from npmjs.org
@eona-x:registry=https://gitlab.eona-x.org/api/v4/groups/299/-/packages/npm/

# key the token on the SAME path as the registry line above: npm walks the registry
# URL's own path looking for a match, so a token on any other path is never sent
//gitlab.eona-x.org/api/v4/groups/299/-/packages/npm/:_authToken=${GITLAB_NPM_TOKEN}

299 is the eona-x group — GitLab's npm endpoint takes the numeric id, not the group path, which is why it is written out rather than spelled. Keep the token in the environment and let npm interpolate it, rather than committing a .npmrc with the secret in it; a token pasted into a repository is the usual way a private registry stops being private.

The two paths must match, and this is the line that costs an afternoon when it does not. npm resolves a credential by walking the registry URL's own path upwards, so a token keyed on /api/v4/packages/npm/ is never offered for a registry at /api/v4/groups/299/-/packages/npm/ — the first is not a prefix of the second. npm then requests the package anonymously and the private registry answers 401, which reads as a credentials problem rather than as the configuration error it is. //gitlab.eona-x.org/:_authToken= on its own is the other correct answer, and the better one if the same machine also talks to other endpoints on this instance.

sh
npm install @eona-x/ui-toolkit-react

React 19 or newer is a peer dependency, and for a specific reason rather than a general preference: NavDropdown renders the inert attribute, which React only added to its DOM attribute types in 19. The package declares no runtime dependency of its own.

16
react

The stylesheets are still the contract

The React components carry the same class names as the Yew ones and no styles of their own, so the rule from section 12 is unchanged: without tokens.css and components.css they render as unstyled HTML. Both are required and, as section 12 explains, their relative order is not. An app that renders OntoBadge — which is one of the 33 exports — needs ontology.css as well.

That package does not exist yet either.

The stylesheets are Rust constants today. @eona-x/ui-toolkit-css is where they are going, as Layer 3 of eona-x/backlog#808 — a different issue from the component package's #809, and the one that will ship first. That is also why the npm package declares no dependencies: it would rather ship no stylesheet than a copy that drifts from the one the Yew consumers get.

Do not import this repository's assets/components.css into an app.

It is written for a page this crate owns end to end, so it opens with a page-shell reset on *, html, body, img, a, h1–h4, p and code (assets/components.css:13-30) and sets html > body { padding-top: 156px } at :393 to reserve room for SiteHeader. Imported whole into an existing React app it resets the host's typography and pushes the whole page down 156 pixels. A Yew host that renders the full page shell wants exactly those rules, which is why they are still there; a component consumer does not.

The published CSS will be scoped, so the markup needs a wrapper.

Layer 3 rewrites every rule in components.css and ontology.css as a descendant of .eona-ui and emits .eona-ui { display: contents } so the wrapper costs no layout — that scoping is what drops the seven page-shell rules above and keeps the stylesheet from restyling the host. It also means a component rendered outside a .eona-ui ancestor gets none of the rules. tokens.css is the exception and passes through unscoped, because custom properties have to be declared on the host document for data-theme to reach anything.

tsx
// what it will look like — in the app's root layout, before anything that renders a component
import '@eona-x/ui-toolkit-css/tokens.css';      // :root custom properties, unscoped
import '@eona-x/ui-toolkit-css/components.css';  // every rule scoped under .eona-ui

// and the markup needs that class somewhere above it; `display: contents` means
// the wrapper adds no box of its own
<div className="eona-ui">
  <Button label="Get started" href="/docs" />
</div>
17
react

No WebAssembly, no client boundary

The package is TypeScript and nothing else. The Rust runs here, at generation time, and none of it survives into what a consumer installs — no WebAssembly, no loader, no bundler plugin, no asynchronous initialisation before the first render. This is worth saying because a Rust-derived component library is normally exactly the thing that ships a .wasm and asks the host to wire it up.

For the same reason the string "use client" does not occur anywhere in the package. Every component is a pure function of its props with no hooks, no state and no event handlers, so each one renders inside a React Server Component — no client bundle, no hydration, no boundary to declare.

A property, not an accident.

It follows from the generator's scope rule rather than from discipline: a component that held state could not be rendered to fixed markup and so would never have been generated in the first place. Interaction stays the host's — a hand-written client component that wraps these, exactly as demo-page.js is the Yew page's glue. The one genuinely stateful thing this crate used to carry was not ported to React; it went to eona-vocabulary-ui as Rust.

18
react

What React does not get, and why

Five components have no React counterpart. The generator ships a component only when it can prove the rendered markup is that component's props passed through unchanged; four of these five derive their markup from their props instead, so there is nothing to splice a prop back into.

Swatch

Recorded as panics while rendering a sentinel — which is what section 09 and index.ts print — and the panic and the derivation are one fact, not two. contrast_color (src/molecules/swatch.rs:7-16) picks a foreground by parsing the hex prop, so the markup is a function of the value rather than a slot the value goes into; :11 slices &hex[0..2], and a sentinel's first character is three or four bytes, so byte 2 is never a character boundary. Making the panic go away would make it worse: with a hex-safe probe alphabet every channel parses as zero, the foreground freezes to white, and the gate emits an unreadable component quietly instead of refusing one loudly.

PaletteGroup

Renders a Swatch per colour (src/molecules/palette_group.rs:52), so the panic propagates — and so does the derivation. Fixing Swatch by lifting the foreground out into a prop does not release this one: it would then have to compute that prop for each colour, and its markup stays a transform of colors. Both levels have to be lifted, which is what abi/overrides.toml records.

DatasetCard

Derives the placeholder initial from the first character of title, for entries with no thumbnail. The rest of the card passes through faithfully, but a component is emitted whole or not at all.

OntoAnnotation

Shortens a datatype IRI to its local name — the part after the last # or /. A React port that passed the prop straight through would print the whole IRI where Yew prints string.

Modal

A different reason: inert is hardcoded, so the dialog has an open state that cannot be rendered and only the closed one can be proven. Tracked as #809; it needs an open: bool prop upstream before there is anything to generate.

Each is recorded in abi/overrides.toml with the line that forces it, and listed in the package's index.ts — because a component missing on purpose and one missing by accident look identical from the outside, and only one of them is a bug.

Both ways out are open.

Hand-port the component in the consuming app, or lift the transform out of the Rust component — pass the initial, or the already-shortened datatype, in as a prop — and it becomes generable and regenerates on the next run. The second fix is usually the better one: it is also the change that lets a Yew caller supply its own value.

19
react

Generated, never hand-edited

Every file under packages/react/src is written by cargo xtask bridgegen from the Rust in src/. Each component is rendered by Yew's own server renderer with sentinel values in its props, and JSX is spliced over the byte ranges where those sentinels landed — so the markup React ships is markup Yew produced, not markup somebody re-implemented from it.

So a bug in a React component is a bug in a Rust component. The file:line it came from is in the header of every generated file; fix it there and regenerate. A hand edit does not survive, and it does not survive quietly either:

sh
cargo xtask bridgegen   # regenerate packages/react and abi/ from src/
cargo xtask check       # is packages/react what this src/ would produce? drift fails

That gate is what makes "the same component in two stacks" a checkable claim rather than an intention. Without it the two would drift apart on the first hotfix applied to whichever copy was in front of somebody.

Equivalent output, not byte-identical output.

The elements, attributes and class names match the Yew render; the serialisation does not, in four measured ways that change nothing a browser shows. Yew writes an author's style string verbatim, trailing semicolon and all, while React serialises a style object; React normalises whitespace inside style and Yew's Classes collapses whitespace inside class where React leaves it; and React 19 hoists a <link rel="preload" as="image"> for every image it renders. Do not write a snapshot test that diffs this package's SSR bytes against the portal's — it will fail on the first render and tell you nothing.

20
ontology

Term badges and annotations

Two components from the ontology tier that hold no state, so they render here like any other. OntoBadge tags an RDF/OWL entity kind, a language or a datatype; each variant takes a charter hue rather than a colour of its own.

rust
<OntoBadge label="Class" variant={TermKind::Class.into()} />
<OntoBadge label="Object Property" variant={TermKind::ObjectProperty.into()} />
<OntoBadge label="en" variant={BadgeVariant::Lang} />
<OntoBadge label="Deprecated" variant={BadgeVariant::Deprecated} />
OntoBadge props
PropTypeDefaultNotes
labelAttrValuerequiredBadge text.
variantBadgeVariantDefaultA TermKind, or Lang / Datatype / Deprecated / Default. Picks the colour pair.
titleOption<AttrValue>NoneNative tooltip, e.g. the full datatype IRI behind a short label.

An annotation row is one predicate and its literal values, each carrying its own language or datatype chip. A value with neither gets no chip.

rdfs:comment
A journey undertaken by a passenger across one or more modes of transport.en
Un trajet effectué par un passager sur un ou plusieurs modes de transport.fr
dcterms:issued
2026-03-14date
rust
<OntoAnnotation
    label="rdfs:comment"
    values={vec![
        LiteralValue { value: "A journey…".into(), language: Some("en".into()), datatype: None },
        LiteralValue { value: "Un trajet…".into(), language: Some("fr".into()), datatype: None },
    ]}
/>
21
tokens

Theming

The charter's palette is fixed — tokens named after their ink. The crate no longer renders it: the colour and declination sections moved to developer.eona-x.eu, and what stayed here is the token layer itself, in TOKENS_CSS. On top of the palette sits a second layer named after the job, and that is the one a theme changes. Every value resolves to a palette token; dark mode never introduces a colour the charter does not already have.

light (default)
Surface, raised

Body text on a raised surface, with a link.

Muted secondary text
data-theme="dark"
Surface, raised

Body text on a raised surface, with a link.

Muted secondary text
html
<!-- opt in on the root element; there is no automatic switch -->
<html data-theme="dark">
Opt-in, deliberately.

Dark mode is not wired to prefers-color-scheme. A consumer that never sets the attribute keeps the appearance it has today — this very page would otherwise flip to dark for any visitor whose operating system is, which is not a decision a brand reference should make on its own.

Dark values come from the charter's own websiteDarkNeutrals (slide 5): Navy Deep for the page, Slate for cards. --eona-accent becomes Lavender rather than staying Action Blue, which is measured rather than taste — #006EF0 on #091B3B is 3.64:1, under the 4.5:1 floor for body text, where Lavender is 11.18:1.

One limit worth knowing: COMPONENTS_CSS still reads the raw palette directly for most rules, so the gallery components in sections 01-08 do not follow data-theme yet. What does follow it is on this page to compare: the two panes above are built from the semantic layer, and so are the badge and annotation classes in ONTOLOGY_CSS.

22
ontology

Where the browser went

Everything in this crate is presentational: no use_state, no Callback, identical output under SSR and CSR. The ontology browser needed the opposite — clipboard writes, localStorage, expand/collapse — so it moved back to eona-vocabulary-ui, the app that wrote it and its only consumer. The two components above stayed, because they hold no state and this page renders them.

Which is why there is nothing to click here.

This page is rendered by ServerRenderer at build time. What a consumer still gets from this crate is the model the two components are typed on — TermKind and LiteralValue — which an app that owns the rest of the model must re-export rather than redefine, or its terms will not fit these props.

rust
use eona_ui_toolkit::ontology::{LiteralValue, TermKind};
use eona_ui_toolkit::{BadgeVariant, OntoAnnotation, OntoBadge};

html! {
    <>
        <OntoBadge label="Class" variant={BadgeVariant::Kind(TermKind::Class)} />
        <OntoAnnotation label="Definition" values={definitions} />
    </>
}
23
ontology

Dataset cards

A catalog entry: thumbnail, title, version, publisher, description, and a call to action. It replaces edc-web-components' PatternFly card, which rendered a second design system's brand inside EONA-X pages and pulled PatternFly's entire stylesheet in to do it.

rust
<DatasetCard
    title="EONA-X Mobility"
    href="ontologies?ontology=eona-mobility"
    version="1.2.0"
    publisher="EONA-X"
    badge="OWL"
    description="Core mobility vocabulary…"
/>
A link, not a button.

The card it replaces took an on_offer_click callback that every caller resolved to a navigation. An href does the same job, keeps the component presentational, and restores middle-click, open-in-new-tab and a status-bar preview.

DatasetCard props
PropTypeDefaultNotes
titleAttrValuerequiredEntry name.
hrefAttrValuerequiredWhere the whole card points.
descriptionOption<AttrValue>NoneShort summary.
versionOption<AttrValue>NoneFree text, not parsed — publishers write what they write.
publisherOption<AttrValue>NoneShown under the title.
thumbnailOption<AttrValue>NoneImage URL; absent renders a lettered placeholder.
badgeOption<AttrValue>NoneCategory chip over the thumbnail, e.g. "OWL".
badge_classOption<AttrValue>NoneExtra class for per-category colouring.
action_labelAttrValue"View documentation"Call-to-action text.