# ray-elements > Web components (custom elements, Shadow DOM, no dependencies) for any web app: plain HTML, React, Vue or a raylang app. npm: `@raylang/elements`, `@raylang/react`, `@raylang/vue`; raylang: the `elements` package. This file describes version 0.9.0. ray-elements is a design system of standard web components (custom elements with Shadow DOM, no dependencies): ``, ``, ``… They work in plain HTML, React, Vue, a browser or any webview, and get extras in a raylang app (a native bridge to the program). The rules below are what this library does differently from what you may assume; the reference of every component follows them. ## Load it ```html ``` - The bare name (`"@raylang/elements"`) resolves with a bundler (Vite, esbuild…) and in a raylang app (raylang serves the package with an import map). A plain page without either imports the files: `import { notify } from "./node_modules/@raylang/elements/index.js";` (everything), `./node_modules/@raylang/elements/components//.js` (one component), `./node_modules/@raylang/elements/core.js` (the API), or declares an import map: ``. - `npm install @raylang/elements` (React: also `@raylang/react`; Vue: also `@raylang/vue`). With a bundler: `import "@raylang/elements/tokens.css";`. - Only some components: `import "@raylang/elements/button";` — one entry per component directory (the "Import" line of each component below). Each brings what it uses. `import { notify } from "@raylang/elements/notification";` gets a component's exports with it. - The API without registering any element: `import { setTheme } from "@raylang/elements/core";`. Note that `import { RayButton } from "@raylang/elements"` loads everything (that entry registers every element). - Avoid a flash of unstyled tags: ``. ## Rules - **Attributes are kebab-case, properties camelCase**: `full-width` / `fullWidth`. Boolean attributes are present or absent (`disabled`, never `disabled="false"`). Properties marked "property only" have no attribute: set them from JS (arrays, objects, functions). The exception is `checked` and `value` on form controls: their attribute is the initial state (see Forms). - **Shared vocabulary**: `size` = `xs` | `sm` | `md` | `lg` | `xl` (default `sm`); `radius` = `xs`…`xl` | `full`; `color` = a palette color (`blue`, `red`, `teal`, `gray`…) or any CSS color, default the primary; `variant` per component (button: `filled`, `light`, `outline`, `subtle`, `default`, `transparent`, `gradient`, `raised`). Use only the values the reference lists. - **Slots have exact names**: an icon in a button goes in `slot="left"` or `slot="right"` (not `start`/`end`, not `prefix`): `Add`. Content without a `slot` goes to the default slot. Check the slot list of the component. - **Never hard-code colors, spacing, radii or shadows** in your own CSS: use the tokens (`var(--ray-background)`, `var(--ray-foreground)`, `var(--ray-border)`, `var(--ray-space-md)`, `var(--ray-radius-default)`, `var(--ray-shadow-sm)`). They follow light and dark themes; literal colors do not. Prefer the semantic tokens over the palette (`--ray-blue-6`). - **Style components from outside** with `color`/`variant`/`size`/`radius`, the tokens (custom properties pierce the shadow root) and `::part()` (the parts each lists). Do not reach into their `shadowRoot` or depend on its markup. - **Layout components exist**: `ray-stack` (vertical), `ray-group` (horizontal), `ray-grid`, `ray-container`, `ray-center`, `ray-card` (a heading of your own in its `header` slot), `ray-app-shell`. Their `gap`/`padding` take `xs`…`xl`. - **Icons**: ``, from the registry (a small set, listed in the reference: there is no `save` or `trash`); decorative unless it has `label`. An icon-only button is `` (`label` is required: it is the accessible name). Add or replace icons with `setIcons({ inbox: '…' })` (the style of the built-in set: 24×24, a 2px `currentColor` stroke) from your own code, never from user input. `filled` draws an icon's filled version for a state (`` when it is a favorite); `"name:filled"` in `setIcons` adds one. An icon the set lacks (`trash`, `save`): register it from Lucide, the same grid and stroke — `import { Trash2 } from "lucide-static"; setIcons({ trash: Trash2 })` (or its SVG pasted in, without a bundler); never draw your own paths. To change one control's icon (only the chips' ×), register its role (`setIcons({ "ui.remove": … })`, roles listed in the reference). Under `dir="rtl"` the roles that point along the text (`ui.next`…) mirror by themselves; an icon of yours that points (back, send) takes `mirror` (``). - **Overlays are in the top layer**: `ray-dialog` is a native modal ``; popovers, menus, tooltips and notifications are `[popover]` elements. Never fight them with `z-index` or `overflow`. ## An app's frame ```html Commands
…
``` `ray-app-bar` is the header on the desktop too (its `label` is the title; several elements may share the `end` slot). On narrow screens the sidebar becomes a drawer and the shell switches to a phone layout by itself. ## Forms - Inputs are **form-associated**: inside a `
` they submit under `name`, validate (`required`, `pattern`, `min`…), reset with it and follow a disabled `
`. Read values with `new FormData(form)` or the element's `.value` / `.checked`. - The `value` / `checked` **attributes are the initial value**; the properties hold the current one. Set `el.value = …` to change it. - Fields take `label`, `description` and `error` (the error text also marks it invalid): do not wrap them in your own `