Skip to main content

AdminForth Components Library

AFCL is a set of components which you can use as build blocks in your AdminForth application. AFCL allows to keep the design consistent with minimal efforts and build new pages faster. AFCL components follow styling standard and respect theme colors.

Components set is always growing, so you can expect new components to be added in the future.

Button

import { Button } from '@/afcl'
<Button @click="doSmth" 
:loader="false" class="w-full">
Your button text
</Button>

<Button @click="doSmth"
:loader="true" class="w-full mt-4">
Your button text
</Button>

AFCL Button

loader prop would show loader when it's true.

import { Link } from '@/afcl'
<Link to="/login">Go to login</Link>

AFCL Link

LinkButton

Looks like button but works like link. Uses router-link under the hood.

import { LinkButton } from '@/afcl'
<LinkButton to="/login">Go to login</LinkButton>

AFCL LinkButton

Select

import { Select } from '@/afcl'


const selected = ref(null)

Single

<Select
class="w-full"
:options="[
{label: 'Last 7 days', value: '7'},
{label: 'Last 30 days', value: '30'},
{label: 'Last 90 days', value: '90'},
{label: 'None', value: null}
]"
v-model="selected"
></Select>

AFCL Select

Multiple

<Select
class="w-full"
:options="[
{label: 'Last 7 days', value: '7'},
{label: 'Last 30 days', value: '30'},
{label: 'Last 90 days', value: '90'},
{label: 'None', value: null}
]"
v-model="selected"
multiple
></Select>

AFCL Select

Custom slots for item

You can customize item and selected item using slots.

<Select
class="w-full"
:options="[
{label: 'Last 7 days', value: '7', records: 110},
{label: 'Last 30 days', value: '30', records: 320},
{label: 'Last 90 days', value: '90', records: 310},
{label: 'None', value: null}
]"
v-model="selected"
>
<template #item="{option}">
<div>
<span>{{ option.label }}</span>
<span class="ml-2 opacity-50">{{ option.records }} records</span>
</div>
</template>
<template #selected-item="{option}">
<span>{{ option.label }} 💫</span>
</template>
</Select>

AFCL Select custom item

Extra item

You might need to put some extra item at bottom of list

<Select
class="w-full"
:options="[
{label: 'Last 7 days', value: '7'},
{label: 'Last 30 days', value: '30'},
{label: 'Last 90 days', value: '90'},
]"
v-model="selected"
>
<template #extra-item>
<LinkButton to="/ranges">Manage ranges</LinkButton>
</template>

</Select>

AFCL Select extra item

Input

import { Input } from '@/afcl'
<Input type="number" class="w-full">
<template #suffix>
USD
</template>
</Input>

AFCL Input

Tooltip

Wrap an element on which you would like to show a tooltip with the Tooltip component and add a tooltip slot to it.

import { Tooltip } from '@/afcl'
<Tooltip>
<a :href="`https://google.com?q=adminforth`" target="_blank" >
<IconCardSearch class="w-5 h-5 me-2"/>
</a>

<template #tooltip>
Search for AdminForth
</template>
</Tooltip>

AFCL Tooltip

VerticalTabs

Wrap each tab lable in tamplate with v-slot value tab:TAB_ALIAS. Wrap each tab content in tamplate with v-slot value TAB_ALIAS. TAB_ALIAS is a unique identifier for each tab here. Place all templates inside VerticalTabs component.

import { VerticalTabs } from '@/afcl'
import { IconGridSolid, IconUserCircleSolid } from '@iconify-prerendered/vue-flowbite';
<VerticalTabs>
<template #tab:Profile>
<IconUserCircleSolid class="w-5 h-5 me-2"/>
Profile
</template>
<template #tab:Dashboard>
<IconGridSolid class="w-5 h-5 me-2"/>
Board
</template>
<template #Profile>
<h3 class="text-lg font-bold text-gray-900 dark:text-white mb-2">Profile Tab</h3>
<p class="mb-2">This is some placeholder content the Profile tab's associated content</p>
</template>
<template #Dashboard>
Dashboard Tab Content
</template>
</VerticalTabs>

AFCL VerticalTabs

Checkbox

import { Checkbox } from '@/afcl'
const enable = ref(false)
<Checkbox v-model="enable">
Enable
</Checkbox>

AFCL Checkbox

Dropzone

import { Ref } from 'vue'
import { Dropzone } from '@/afcl'

const files: Ref<File[]> = ref([])

watch(files, (files) => {
console.log('files selected', files);
setTimeout(() => {
// clear
files.length = 0;
}, 5000);
})
<Dropzone
:extensions="['.jpg', '.jpeg', '.png']"
:maxSizeBytes="1024 * 1024 * 2"
:multiple="false"
v-model="files"
/>

AFCL Dropzone

Table

import { Table } from '@/afcl'
<Table
:columns="[
{ label: 'Name', fieldName: 'name' },
{ label: 'Age', fieldName: 'age' },
{ label: 'Country', fieldName: 'country' },
]"
:data="[
{ name: 'John', age: 30, country: 'US' },
{ name: 'Rick', age: 25, country: 'CA' },
{ name: 'Alice', age: 35, country: 'UK' },
{ name: 'Colin', age: 40, country: 'AU' },
]"
></Table>

AFCL Table

No even highlights

<Table
:columns="[
{ label: 'Name', fieldName: 'name' },
{ label: 'Age', fieldName: 'age' },
{ label: 'Country', fieldName: 'country' },
]"
:data="[
{ name: 'John', age: 30, country: 'US' },
{ name: 'Rick', age: 25, country: 'CA' },
{ name: 'Alice', age: 35, country: 'UK' },
{ name: 'Colin', age: 40, country: 'AU' },
]"
:evenHighlights="false"
></Table>

AFCL Table withut even highlights

Custom cell

import { Table } from '@/afcl'
const isoFlagToEmoji = (iso) => iso.toUpperCase().replace(/./g, char => String.fromCodePoint(char.charCodeAt(0) + 127397))
<Table
:columns="[
{ label: 'Name', fieldName: 'name' },
{ label: 'Age', fieldName: 'age' },
{ label: 'Country', fieldName: 'country' },
]"
:data="[
{ name: 'John', age: 30, country: 'US' },
{ name: 'Rick', age: 25, country: 'CA' },
{ name: 'Alice', age: 35, country: 'BR' },
{ name: 'Colin', age: 40, country: 'AU' },
]"
>
<template #cell:country="{item}">
{{ isoFlagToEmoji(item.country) }}
</template>
</Table>

AFCL Table with custom cell

Custom header

        <Table
:columns="[
{ label: 'Name', fieldName: 'name' },
{ label: 'Age', fieldName: 'age' },
{ label: 'Country', fieldName: 'country' },
]"
:data="[
{ name: 'John', age: 30, country: 'US' },
{ name: 'Rick', age: 25, country: 'CA' },
{ name: 'Alice', age: 35, country: 'BR' },
{ name: 'Colin', age: 40, country: 'AU' },
]"
>
<template #header:country>
🌍 Geo
</template>
</Table>

AFCL Table with custom header

Pagination

Table provides front-end side pagination. You can set pageSize (default is 10) to set how many rows to show per page. If there is less then pageSize rows, pagination will not be shown.

<Table
:columns="[
{ label: 'Name', fieldName: 'name' },
{ label: 'Age', fieldName: 'age' },
{ label: 'Country', fieldName: 'country' },
]"
:data="[
{ name: 'John', age: 30, country: 'US' },
{ name: 'Rick', age: 25, country: 'CA' },
{ name: 'Alice', age: 35, country: 'BR' },
{ name: 'Colin', age: 40, country: 'AU' },
]"
:pageSize="3"
>
</Table>

AFCL Table with pagination

ProgressBar

 <ProgressBar
:currentValue="2600"
:minValue="0"
:maxValue="5000"
/>

ProgressBar

Custom labels

Custom labels in the ProgressBar component allow you to customize the text displayed on the left and right sides of the progress bar. You can also customize the format of the value and the progress text.

<ProgressBar
:currentValue="1070"
:minValue="0"
:maxValue="5000"
:leftLabel="'Level 2'"
:rightLabel="'Level 3'"
:formatter="(value: number) => `${value} points`"
:progressFormatter="(value: number, percentage: number) => `${value} done`"
/>

ProgressBar

Skeleton

Skeleton component is used to display a loading state for a component. You can use prop type to set the type of the skeleton.

  <div class="flex flex-col gap-2">
<Skeleton class="w-full h-4" />
<Skeleton class="w-full h-2" />
<Skeleton class="w-full h-2" />
<Skeleton class="w-full h-2" />
<Skeleton class="w-full h-2" />
</div>

Spinner

Skeleton image

<Skeleton type="image" class="w-full h-full" />

Skeleton type

Skeleton video

<Skeleton type="video" class="w-full h-full" />

Skeleton type video

Skeleton avatar

<Skeleton type="avatar" class="w-20 h-20" />

Skeleton avatar

Spinner

Spinner component is used to display a loading state for a component.

<Spinner class="w-10 h-10" />

Spinner

Bar Chart

Under the hood AdminForth uses MIT-licensed ApexCharts. It has very rich variety of options, you can pass any of native settings to options prop. Here we will only show some basics.

<BarChart
:data="[
{ count: 1, x: '02 Jun 2025'},
{ count: 5, x: '03 Jun 2025'},
{ count: 3, x: '04 Jun 2025'},
{ count: 4, x: '05 Jun 2025'},
{ count: 2, x: '06 Jun 2025'},
]"
:series="[{
name: $t('Added apartments'),
fieldName: 'count',
color: '#4E79A7',
}]"
:options="{
chart: {
height: 250,
},
}"
/>

Bar chart

Y-axis labels

<BarChart
:data="[
{ count: 1, x: '02 Jun 2025'},
{ count: 5, x: '03 Jun 2025'},
{ count: 3, x: '04 Jun 2025'},
{ count: 4, x: '05 Jun 2025'},
{ count: 2, x: '06 Jun 2025'},
]"
:series="[{
name: $t('Added apartments'),
fieldName: 'count',
color: '#4E79A7',
}]"
:options="{
chart: {
height: 250,
},
yaxis: {
stepSize: 1, // needed if your data is integer
labels: {
show: true,
style: {
fontFamily: 'Inter, sans-serif',
cssClass: 'text-xs font-normal fill-gray-500 dark:fill-gray-400'
}
}
}
}"
/>

BarChart y Axis labels

X-axis labels and formatting labels

<BarChart
:data="[
{ count: 1, x: '02 Jun 2025'},
{ count: 5, x: '03 Jun 2025'},
{ count: 3, x: '04 Jun 2025'},
{ count: 4, x: '05 Jun 2025'},
{ count: 2, x: '06 Jun 2025'},
]"
:series="[{
name: $t('Added apartments'),
fieldName: 'count',
color: '#4E79A7',
}]"
:options="{
chart: {
height: 250,
},
xaxis: {
labels: {
show: true,
formatter: function (value) {
return dayjs(value).format('DD MMM');
},
style: {
fontFamily: 'Inter, sans-serif',
cssClass: 'text-xs font-normal fill-gray-500 dark:fill-gray-400'
}
}
}
}"
/>

BarChart x Axis labels

Grid

<BarChart
:data="[
{ count: 1, x: '02 Jun 2025'},
{ count: 5, x: '03 Jun 2025'},
{ count: 3, x: '04 Jun 2025'},
{ count: 4, x: '05 Jun 2025'},
{ count: 2, x: '06 Jun 2025'},
]"
:series="[{
name: $t('Added apartments'),
fieldName: 'count',
color: '#4E79A7',
}]"
:options="{
chart: {
height: 250,
},
grid: {
show: true,
borderColor: 'rgba(0, 0, 0, 0.1)',
strokeDashArray: 4,
position: 'back',
xaxis: {
lines: {
show: true
}
},
yaxis: {
lines: {
show: true
}
}
}
}"
/>

BarChart grid

Data labels

<BarChart
:data="[
{ count: 1, x: '02 Jun 2025'},
{ count: 5, x: '03 Jun 2025'},
{ count: 3, x: '04 Jun 2025'},
{ count: 4, x: '05 Jun 2025'},
{ count: 2, x: '06 Jun 2025'},
]"
:series="[{
name: $t('Added apartments'),
fieldName: 'count',
color: '#4E79A7',
}]"
:options="{
chart: {
height: 250,
},
dataLabels: {
formatter: function (value) {
return `⬆️ ${value}`;
},
enabled: true,
style: {
fontSize: '12px',
fontFamily: 'Inter, sans-serif',
}
}
}"
/>

BarChart data labels

Stacked bars and legend

<BarChart
:data="[
{ countCars: 2, countBikes: 3, x: '02 Jun 2025'},
{ countCars: 5, countBikes: 1, x: '03 Jun 2025'},
{ countCars: 3, countBikes: 4, x: '04 Jun 2025'},
{ countCars: 4, countBikes: 2, x: '05 Jun 2025'},
{ countCars: 2, countBikes: 3, x: '06 Jun 2025'},
]"
:series="[
{
name: $t('Cars'),
fieldName: 'countCars',
color: '#4E79A7',
},
{
name: $t('Bikes'),
fieldName: 'countBikes',
color: '#F28E2B',
}
]"
:options="{
chart: {
height: 250,
stacked: true,
},
legend: {
show: true,
},
}"
/>

BarChart stacked bars

Horizontal bars

<BarChart
:data="[
{ countCars: 2, countBikes: 3, x: '02 Jun 2025'},
{ countCars: 5, countBikes: 1, x: '03 Jun 2025'},
{ countCars: 3, countBikes: 4, x: '04 Jun 2025'},
{ countCars: 4, countBikes: 2, x: '05 Jun 2025'},
{ countCars: 2, countBikes: 3, x: '06 Jun 2025'},
]"
:series="[
{
name: $t('Cars'),
fieldName: 'countCars',
color: '#4E79A7',
},
{
name: $t('Bikes'),
fieldName: 'countBikes',
color: '#F28E2B',
}
]"
:options="{
chart: {
height: 250,
type: 'bar',
},
plotOptions: {
bar: {
horizontal: true,
}
},
legend: {
show: true,
},
}"
/>

BarChart horizontal bars

Area Chart

import { AreaChart } from '@/afcl'
<AreaChart
:data="[
{ count: 1, x: '02 Jun 2025'},
{ count: 5, x: '03 Jun 2025'},
{ count: 3, x: '04 Jun 2025'},
{ count: 4, x: '05 Jun 2025'},
{ count: 2, x: '06 Jun 2025'},
]"
:series="[{
name: $t('Added apartments'),
fieldName: 'count',
color: '#4E79A7',
}]"
:options="{
chart: {
height: 250,
},
}"
/>
</div>
<div>
![AreaChart](image-68.png)
</div>
</div>

### Multiple lines

<div class="split-screen" >

<div >
```html
<AreaChart
:data="[
{ countCars: 2, countBikes: 3, x: '02 Jun 2025'},
{ countCars: 5, countBikes: 1, x: '03 Jun 2025'},
{ countCars: 3, countBikes: 4, x: '04 Jun 2025'},
{ countCars: 4, countBikes: 2, x: '05 Jun 2025'},
{ countCars: 2, countBikes: 3, x: '06 Jun 2025'},
]"
:series="[
{
name: $t('Cars'),
fieldName: 'countCars',
color: '#4E79A7',
},
{
name: $t('Bikes'),
fieldName: 'countBikes',
color: '#F28E2B',
}
]"
:options="{
chart: {
height: 250,
},
}"
/>

AreaChart multiple lines

Stacked area

<AreaChart
:data="[
{ countCars: 2, countBikes: 3, x: '02 Jun 2025'},
{ countCars: 5, countBikes: 1, x: '03 Jun 2025'},
{ countCars: 3, countBikes: 4, x: '04 Jun 2025'},
{ countCars: 4, countBikes: 2, x: '05 Jun 2025'},
{ countCars: 2, countBikes: 3, x: '06 Jun 2025'},
]"
:series="[
{
name: $t('Cars'),
fieldName: 'countCars',
color: '#4E79A7',
},
{
name: $t('Bikes'),
fieldName: 'countBikes',
color: '#F28E2B',
}
]"
:options="{
chart: {
height: 250,
stacked: true,
},
}"
/>

AreaChart stacked area

Data labels

<AreaChart
:data="[
{ count: 2, x: '02 Jun 2025'},
{ count: 5, x: '03 Jun 2025'},
{ count: 3, x: '04 Jun 2025'},
{ count: 4, x: '05 Jun 2025'},
{ count: 2, x: '06 Jun 2025'},
]"
:series="[
{
name: $t('Cars'),
fieldName: 'count',
color: '#4E79A7',
},
]"
:options="{
chart: {
height: 250,
},
dataLabels: {
enabled: true,
style: {
fontSize: '12px',
fontFamily: 'Inter, sans-serif',
}
},
grid: {
padding: {
left: 10, // to fit the labels
right: 10,
},
},
}"
/>

Area Chart Data Lables

Grid, x-axis and y-axis labels

See Bar Chart for details, the config is the same.

Pie Chart

import { PieChart } from '@/afcl'

Basic

<PieChart
:data="[
{ amount: 5, label: 'Cars'},
{ amount: 3, label: 'Bikes'},
{ amount: 2, label: 'Trucks'},
{ amount: 1, label: 'Boats'},
]"
:options="{
chart: {
height: 250,
},
}"
/>

Pie Chart

Pie with data labels

<PieChart
:data="[
{ amount: 5, label: 'Cars'},
{ amount: 3, label: 'Bikes'},
{ amount: 2, label: 'Trucks'},
{ amount: 1, label: 'Boats'},
]"
:options="{
chart: {
height: 250,
},
dataLabels: {
enabled: true,
},
plotOptions: {
pie: {
dataLabels: {
offset: -10, // Moves labels closer to or further from the slices
minAngleToShowLabel: 10, // Ensures that small slices don’t show labels
},
expandOnClick: true,
},
},
}"
/>

Pie Chart with data labels

Donut Chart

<PieChart
:data="[
{ amount: 5, label: 'Cars'},
{ amount: 3, label: 'Bikes'},
{ amount: 2, label: 'Trucks'},
{ amount: 1, label: 'Boats'},
]"
:options="{
chart: {
height: 250,
type: 'donut',
},
}"
/>

Donut Chart

Fill donut with total info

<PieChart
:data="[
{ amount: 5, label: 'Cars'},
{ amount: 3, label: 'Bikes'},
{ amount: 2, label: 'Trucks'},
{ amount: 1, label: 'Boats'},
]"
:options="{
chart: {
height: 250,
type: 'donut',
},
plotOptions: {
pie: {
donut: {
labels: {
total: {
show: true,
label: $t('Total wheels'),
formatter: () => `11`,
},
},
},
},
},
}"
/>

Donut Chart with total

Radial bar chart

  <PieChart
:data="[
{ amount: 80, label: 'Cars'},
{ amount: 50, label: 'Bikes'},
{ amount: 30, label: 'Trucks'},
{ amount: 70, label: 'Boats'},
]"
:options="{
chart: {
height: '300px',
width: '100%',
type: 'radialBar',
sparkline: {
enabled: true,
},
},
plotOptions: {
radialBar: {
track: {
background: '#E5E7EB',
},
dataLabels: {
name: {
offsetY: -10
},
value: {
offsetY: 2,
}
},
hollow: {
margin: 0,
size: '32%',
}
},
},
legend: {
show: true,
position: 'bottom',
},

}"
/>

Radial Chart