Appearance
UIKit
The UIKit is a library of 38 reusable Vue 3 components in shared/src/uikit/. Each component has its own directory with a consistent file structure and is documented in Storybook. Components are imported via the @uikit/ alias from both frontend/ and admin/.
Running Storybook
bash
pnpm storybook # Opens at http://localhost:6006Component List
Form Controls
| Component | Import | Description |
|---|---|---|
GisButton | @uikit/button/button.vue | Primary action button with variants and sizes |
GisIconButton | @uikit/icon-button/icon-button.vue | Icon-only button |
GisInput | @uikit/input/input.vue | Text input field |
GisPasswordInput | @uikit/password-input/password-input.vue | Password input with toggle visibility |
GisPasswordRequirements | @uikit/password-requirements/password-requirements.vue | Live password-strength checklist |
GisTextarea | @uikit/textarea/textarea.vue | Multi-line text input |
GisSelect | @uikit/select/select.vue | Dropdown select |
GisCheckbox | @uikit/checkbox/checkbox.vue | Checkbox control |
GisRadio | @uikit/radio/radio.vue | Radio button |
GisToggle | @uikit/toggle/toggle.vue | Toggle switch |
GisSlider | @uikit/slider/slider.vue | Range slider |
GisField | @uikit/field/field.vue | Form field wrapper (label + error) |
Data Display
| Component | Import | Description |
|---|---|---|
GisTable | @uikit/table/table.vue | Data table |
GisList | @uikit/list/list.vue | Ordered/unordered list |
GisAvatar | @uikit/avatar/avatar.vue | User avatar |
GisBadge | @uikit/badge/badge.vue | Status badge |
GisTag | @uikit/tag/tag.vue | Keyword tag |
GisChip | @uikit/chip/chip.vue | Closeable chip |
GisProgressBar | @uikit/progress-bar/progress-bar.vue | Linear progress |
GisSkeleton | @uikit/skeleton/skeleton.vue | Content placeholder |
GisEmptyState | @uikit/empty-state/empty-state.vue | No data state |
GisDivider | @uikit/divider/divider.vue | Visual separator |
GisIcon | @uikit/icon/icon.vue | Remix Icon wrapper (globally registered) |
Layout & Containers
| Component | Import | Description |
|---|---|---|
GisCard | @uikit/card/card.vue | Container box |
GisAccordion | @uikit/accordion/accordion.vue | Collapsible sections |
GisTabs / GisTab | @uikit/tabs/tabs.vue | Tabbed content |
GisBreadcrumb | @uikit/breadcrumb/breadcrumb.vue | Navigation breadcrumb |
GisStepper | @uikit/stepper/stepper.vue | Multi-step indicator |
GisPagination | @uikit/pagination/pagination.vue | Page navigation |
Overlays & Feedback
| Component | Import | Description |
|---|---|---|
GisModal | @uikit/modal/modal.vue | Dialog overlay |
GisDrawer | @uikit/drawer/drawer.vue | Side drawer panel |
GisDropdown | @uikit/dropdown/dropdown.vue | Dropdown menu |
GisPopover | @uikit/popover/popover.vue | Floating popover |
GisTooltip | @uikit/tooltip/tooltip.vue | Tooltip overlay |
GisAlert | @uikit/alert/alert.vue | Alert message box |
GisSnackbar | @uikit/snackbar/snackbar.vue | Toast notification |
GisSpinner | @uikit/spinner/spinner.vue | Loading spinner |
Special
| Component | Import | Description |
|---|---|---|
GisCookieBanner | @uikit/cookie-banner/cookie-banner.vue | GDPR cookie consent |
File Structure
Each component follows this structure:
shared/src/uikit/<name>/
├── <name>.vue # Component
├── <name>.scss # Styles (BEM + @apply)
├── <name>.stories.ts # Storybook stories (CSF)
└── types/
└── <name>.types.ts # TypeScript typesUsage
Import components via the @uikit/ alias:
vue
<script setup lang="ts">
import GisButton from '@uikit/button/button.vue';
import GisDrawer from '@uikit/drawer/drawer.vue';
</script>
<template>
<GisButton variant="primary" size="medium" label="Click me" />
<GisDrawer :open="isOpen" @close="isOpen = false">
<p>Drawer content</p>
</GisDrawer>
</template>GisIcon is the only globally registered component — it does not need an import:
vue
<template>
<GisIcon name="ri-map-pin-line" />
</template>Variant System
Components use const arrays for type-safe variants:
typescript
// types/button.types.ts
export const buttonVariants = [
'primary', 'secondary', 'success', 'info',
'warning', 'danger', 'contrast', 'ghost',
] as const;
export const buttonSizes = ['small', 'medium', 'large'] as const;
export type ButtonVariant = (typeof buttonVariants)[number];
export type ButtonSize = (typeof buttonSizes)[number];Styling Pattern
All component styles follow BEM naming with SCSS nesting:
scss
.gis-button {
@apply inline-flex items-center justify-center gap-2 font-normal text-sm;
border-radius: 12px;
&--primary {
@apply bg-primary-600 text-white;
&:hover:not(:disabled) {
@apply bg-primary-700;
}
&:active:not(:disabled) {
@apply bg-primary-800;
}
}
&--small { @apply text-xs h-8 px-3; }
&--medium { @apply text-sm h-10 px-4; }
&--large { @apply text-base h-12 px-5; }
&:disabled { @apply opacity-50 cursor-not-allowed; }
&__icon { @apply text-current; }
&__label { @apply leading-none; }
}All SCSS files are imported in shared/src/uikit/index.scss.
Storybook Stories
Stories use Component Story Format (CSF):
typescript
import GisButton from './button.vue';
import { buttonVariants, buttonSizes } from './types/button.types';
export default {
title: 'UIKit/Button',
component: GisButton,
tags: ['autodocs'],
argTypes: {
variant: { control: 'select', options: buttonVariants },
size: { control: 'select', options: buttonSizes },
},
};
export const Primary = {
args: { label: 'Button', variant: 'primary', size: 'medium' },
};
export const AllVariants = {
render: () => ({
components: { GisButton },
template: `
<div class="flex gap-3">
<GisButton v-for="v in variants" :key="v" :variant="v" :label="v" />
</div>
`,
setup: () => ({ variants: buttonVariants }),
}),
};Rules:
- Title pattern:
UIKit/<ComponentName> - Always include
tags: ['autodocs'] - Use const arrays from types for argType options
- Include individual variant stories + an
AllVariantsgallery