Regira UI (front-end)
@regira/modules/vue/ui — the UI toolkit the entity views render with: components, composables, and plugins for paging, loading, feedback, modal, tabs, icons, autocomplete, form inputs, and responsive layout.
Areas
| Area | Components | Programmatic |
|---|---|---|
| paging | Paging, ResultSummary | usePaging, pagingDefaults, ButtonType, pagingPlugin |
| loading | Loading, LoadingContainer, LoadingButton | loadingPlugin |
| feedback | Feedback, Pending, Success, ErrorSummary | useFeedback, useAppFeedback, toFeedbackError, fieldMessages, fieldLabel, setErrorTranslator, FeedbackStatus, feedbackPlugin, FeedbackError |
| modal | DefaultModal | ModalType, modalPlugin, injectModal |
| tabs | TabContainer, TabNavigation | Tab / ITab |
| icons | Icon, BsIcon, FaIcon, IconButton | iconPlugin, loadIcons |
| screen | — | useScreen, screenPlugin |
| autocomplete | Autocomplete | useAutocomplete |
| buttons | ConfirmButton | — |
| input | Anchor, DateInput, DescriptionInput, FormButtonsRow, FormLabel, FormSection, NullableCheckBox, NullableLabel, FileDropZone, CopyToClipboardButton | — |
| gis | GMap, GMapLink, GMapButton (Google Maps) | — |
Plugins & imports
Components are imported locally from @regira/modules/vue/ui (or a sub-path) by default — no component is registered globally. The whole kit works à la carte: nearly every component or composable drops into any Vue 3 app on its own, with no entity scaffold, no plugin stack, and no other module required — lean and headless builds included. The one exception is Autocomplete: its template uses v-click-outside, so the click-outside directive plugin from @regira/modules/vue/directives must be installed or the directive fails to resolve and the dropdown never closes:
import { clickOutside } from "@regira/modules/vue/directives"
app.use(clickOutside) // required by AutocompleteImport the library styles once in main.ts:
import "@regira/modules/style.css" // --rg-* tokens, modal backdrop, autocomplete dropdown, the list layout rulesInstall the plugins for the areas you use; each configures app-wide state only:
feedbackPlugin→$feedback·screenPlugin→$screeniconPlugin→ glyph source (bs/fa) + friendly icon keys,$iconsloadingPlugin→ the imageLoading/LoadingContainerrender (a built-in spinner when there is none) ·pagingPlugin→Pagingpage size
To opt back into app-wide registration, set configureGlobals({ registerComponentsGlobally: true }) (from @regira/modules/vue/ioc) before installing the plugins. With the flag on, iconPlugin registers Icon/IconButton, loadingPlugin registers Loading/LoadingButton/LoadingContainer, pagingPlugin registers Paging, and modalPlugin registers MyModal — so those tags resolve without local imports. Each of these plugins takes matching component options (e.g. loadingPlugin { Loading? }, pagingPlugin { Paging? }, compile-checked against the props contract) that swap what gets registered — and loadingPlugin { Loading }, like modalPlugin { Modal }, also swaps the indicator inside library components via injectLoading().
Icon works without iconPlugin (defaults to Bootstrap glyphs) and DefaultModal needs no plugin; the glyph font CSS (bootstrap-icons/Font Awesome) must be imported separately. Icon takes a registered friendly key or a raw icon class. Most-used in entity UIs: Paging, LoadingContainer, Feedback, TabContainer + Tab.create, Icon, and useScreen.
Customizing the look
The default styling is deliberately plain Bootstrap 5 — restyling is encouraged and expected. Five layers, cheapest first:
- Theme tokens — override the
--rg-*CSS variables (+ Bootstrap component-level vars) in the app'stheme.scss, loaded after@regira/modules/style.css. - CSS hooks — every component carries stable
rg-*root/part classes andis-*state classes. - Slots — typed via each component's exported
XxxSlots. - Replace the skin — a new SFC declaring the exported contract (
XxxProps/XxxEmits/XxxSlots) with behavior from the exporteduseXxxcomposable; the modal swaps app-wide viaapp.use(modalPlugin, { Modal })— every library-internal modal resolves it throughinjectModal(). - Eject —
node node_modules/@regira/modules/_template/scaffold.mjs --ui <Component>copies the reference skin into the app, imports rewritten to public API.
rg-* and --rg-* are the library's namespace. Every one you write in an app stylesheet must be a hook the library already ships, overridden — minting .rg-card or --rg-surface reads as library API and collides the day the library ships that name. App-owned classes and tokens take an app prefix (.shop-card, --shop-surface).
Structural classes
The opt-in half of the shipped styling: rules @regira/modules/style.css provides for your markup to wear, rather than hooks you restyle. Put them on your own elements; they need no theme.scss rule.
| Class | Rule | Put it on |
|---|---|---|
entity-list | zeroes the child .row gutter margins + min-width: 0 on the cells, so text-truncate clips instead of widening the row | the element wrapping an overview's header row and its item rows |
entity-list--scroll-x | adds overflow-x: auto | a single list whose row genuinely cannot fit |
italic-muted | opacity: .6; font-style: italic | placeholder/inherited text; NullableLabel's "no label set" fallback |
.entity-list sets no overflow on purpose — don't add one. overflow-x: auto next to a visibleoverflow-y computes to auto on both axes, so the list becomes a scroll container that clips absolutely-positioned descendants (a dropdown menu in a row) and breaks position: sticky inside it. entity-list--scroll-x is the per-list opt-in for the rare row that cannot fit.
Notes
- The barrel
@regira/modules/vue/uire-exports everything except part of the screen module: onlyuseScreenandscreenPluginare re-exported —SCREEN_SIZES,IScreen,IScreenSize, andgetWindowSizeare not, and there is no@regira/modules/vue/ui/screensub-path, so they cannot be imported from the published package.feedback,icons, andmodalalso have dedicated sub-paths; the modalstyle.scssis the one export only a sub-path (/modal) carries. Feedbacklists a failure's field errors under its message, each field headed by its label — the field's key translated as the messages are (fieldLabel), else the key in words,dueDateas "Due date". Errors that belong to no one field show without a heading. A form that shows each field's errors at its input sets:hide-field-errors="true"so they appear once; a field without an input then shows nowhere.useFeedback({ autoHideDelay }): onlysuccess()hides itself,autoHideDelayms later (default 1500;0keeps it up).pending(),fail()andreset()cancel a hide still waiting, so a failure shown right after a success stays up until something resets it.FormLabelrenders below its input (a muted caption, not a<label>above it), so align a row that mixes labelled fields with buttons toflex-start.FormButtonsRowwithreadonlyrenders no buttons: nothing can be saved, deleted or restored, and there are no edits for Cancel to discard. The way back is the page's navigation or the modal's close button.TabContainershows the first visible, enabled tab among the URL hash (withuse-route-nav) or the last selection (without), thenactive, and otherwise the default tab.activeis read once, when the container is created. Withuse-route-navthe hash is written only when a tab is selected — mounting never navigates.selectfires on mount and whenever the tab on screen changes, Back/Forward included; a click on a disabled tab is ignored.- Modal is a component (
DefaultModal+:is-visible— one-way, flip your own state on@close/@cancel/@submit), not anopenModal()composable; for entity edit-in-modal useuseModalfrom the entities module. Autocompleterenders its result panel on<body>(aTeleport) withposition: fixed, so no ancestor'soverflow— a scrollable modal body, anoverflow: hiddencard — clips it. It places the panel against the viewport itself: it opens below the control, flips above it when the results do not fit below and there is more room up there (the field at the foot of a modal), and caps its height to the room on the side it opens to, so a long list scrolls instead of running off screen. It follows the control while open — a scroll, a resize, or the form shifting around it — and hides while the control is scrolled out of view inside its scroll container. Nothing to configure —--rg-dropdown-max-heightstays the ceiling. The panel lives on<body>, outside your markup, so style it throughresultClass/itemsClass/itemClassor a global.autocomplete-itemsrule; a selector scoped to an ancestor (.my-form .autocomplete-items) does not reach it. Neither does anything it would inherit from one: set--rg-dropdown-max-height/--rg-dropdown-zon:rootor.autocomplete-itemsrather than on a container, and a font size, colour ordata-bs-themeon a region around the form leaves the panel on the page's own — give it aresultClassthat matches the region. For assistive technology the input is acomboboxwhosearia-controlsnames the panel'slistboxand whosearia-activedescendantfollows the arrow-key selection, so screen readers keep the two together even though the panel sits elsewhere in the DOM. Label the input as usual (<label for>oraria-label).