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>
loader prop would show loader when it's true.
Link
import { Link } from '@/afcl'
<Link to="/login">Go to login</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>
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>
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>
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>
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>
Input
import { Input } from '@/afcl'
<Input type="number" class="w-full">
<template #suffix>
USD
</template>
</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>
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>
Checkbox
import { Checkbox } from '@/afcl'
const enable = ref(false)
<Checkbox v-model="enable">
Enable
</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"
/>
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>
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>
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>
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>
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>
ProgressBar
<ProgressBar
:currentValue="2600"
:minValue="0"
:maxValue="5000"
/>
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`"
/>
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>
Skeleton image
<Skeleton type="image" class="w-full h-full" />
Skeleton video
<Skeleton type="video" class="w-full h-full" />
Skeleton avatar
<Skeleton type="avatar" class="w-20 h-20" />
Spinner
Spinner component is used to display a loading state for a component.
<Spinner class="w-10 h-10" />
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,
},
}"
/>
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'
}
}
}
}"
/>
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'
}
}
}
}"
/>
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
}
}
}
}"
/>
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',
}
}
}"
/>
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,
},
}"
/>
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,
},
}"
/>
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,
},
}"
/>
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,
},
}"
/>
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,
},
},
}"
/>
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 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,
},
},
}"
/>
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',
},
}"
/>
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`,
},
},
},
},
},
}"
/>
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',
},
}"
/>