Link
Provides a custom <NuxtLink> component to handle any kind of links within your application.
Examples
Basic
| Prop | Default | Type | Description |
|---|---|---|---|
label | - | string | The label of the link. |
activeClass | - | string | The class to apply when the link is active. |
inactiveClass | - | string | The class to apply when the link is inactive. |
exact | false | boolean | Trigger the link active class only on exact matches of the path. |
exactQuery | false | boolean | Trigger the link active class only on exact matches of the query. |
exactHash | false | boolean | Trigger the link active class only on exact matches of the hash. |
active | - | boolean | Force the link to be active regardless of the current route, applying the activeClass. |
Read more in Nuxt Link Props
Preview
Code
<template>
<div class="flex gap-4">
<NLink
to="/components/link"
active-class="text-success"
label="Active Link"
/>
<NLink
to="/components/link/"
exact-active-class="text-lime"
exact
>
Exact Active Link
</NLink>
<NLink
to="https://github.com/una-ui/una-ui"
target="_blank"
inactive-class="text-gray"
>
Inactive Link
</NLink>
<NLink
to="/components/link?search=something"
active-class="text-warning"
exact-query
>
Exact query
</NLink>
<NLink
to="/components/link#something"
active-class="text-pink"
exact-hash
>
Exact Hash
</NLink>
<NLink
to="/components"
active-class="text-blue"
active
>
Active (Force)
</NLink>
</div>
</template>
Disabled
| Prop | Default | Type | Description |
|---|---|---|---|
disabled | false | boolean | Whether the link is disabled. |
Preview
Code
<template>
<NLink
to="/components/button"
label="Disabled Link"
disabled
/>
</template>
Slots
| Name | Props | Description |
|---|---|---|
default | - | The content of the link. |
Props
types/link.ts
import type { NuxtLinkProps } from 'nuxt/app'
export interface NLinkProps extends NuxtLinkProps {
/**
* The label of the link
*/
label?: string
/**
* Manually enable/disable the exact match
*
* @default false
*/
exact?: boolean
/**
* Manually enable/disable the exact match for the query string
*
* @default false
*/
exactQuery?: boolean | 'partial'
/**
* Manually enable/disable the exact match for the hash
*
* @default false
*/
exactHash?: boolean
/**
* Disable the link
*
* @default false
*/
disabled?: boolean
/**
* Force the link to be active independent of the current route.
*
* @default false
*/
active?: boolean
/**
* Active classes to apply when the link is inactive
*
* @example 'text-primary'
*/
inactiveClass?: string
/**
* Useful in combination with `NavLink` to apply the active class to the parent element
*
*/
navLinkActive?: string
/**
* Useful in combination with `NavLink` to apply the inactive class to the parent element
*/
navLinkInactive?: string
}
Presets
shortcuts/link.ts
type LinkPrefix = '_link'
export const staticLink: Record<`${LinkPrefix}-${string}`, string> = {
// base
'_link-disabled': 'n-disabled',
}
export const dynamicLink: [RegExp, (params: RegExpExecArray) => string][] = [
// dynamic preset
]
export const link = [
...dynamicLink,
staticLink,
]
Components
Link.vue
<script lang="ts">
import type { PropType } from 'vue'
import type { NLinkProps } from '../../types'
import { NuxtLink } from '#components'
import { useRoute } from '#imports'
import { diff, isEqual } from 'ohash/utils'
import { defineComponent } from 'vue'
export default defineComponent({
inheritAttrs: false,
props: {
...NuxtLink.props,
// config
label: {
type: String as PropType<NLinkProps['label']>,
default: undefined,
},
/** Force the link to be active independent of the current route. */
active: {
type: Boolean as PropType<NLinkProps['active']>,
default: undefined,
},
exact: {
type: Boolean as PropType<NLinkProps['exact']>,
default: false,
},
exactQuery: {
type: Boolean as PropType<NLinkProps['exactQuery']>,
default: false,
},
exactHash: {
type: Boolean as PropType<NLinkProps['exactHash']>,
default: false,
},
disabled: {
type: Boolean as PropType<NLinkProps['disabled']>,
default: false,
},
// styling
inactiveClass: {
type: String as PropType<NLinkProps['inactiveClass']>,
default: undefined,
},
// TODO: convert to sidebar preset
navLinkActive: {
type: String as PropType<NLinkProps['navLinkActive']>,
default: undefined,
},
navLinkInactive: {
type: String as PropType<NLinkProps['navLinkInactive']>,
default: undefined,
},
},
setup(props: any) {
const route = useRoute()
function resolveLinkClass(route: any, $route: any, { isActive, isExactActive }: { isActive: boolean, isExactActive: boolean }): string | null {
if (props.active === true) {
return props.activeClass
}
if (props.active === false) {
return props.inactiveClass
}
if (props.exactQuery && !isEqual(route.query, $route.query))
return props.inactiveClass
if (props.exactHash && !isEqual(route.hash, $route.hash))
return props.inactiveClass
if (props.exact && isExactActive)
return props.exactActiveClass
if (!props.exact && isActive)
return props.activeClass
return props.inactiveClass
}
function resolveNavLinkActive(route: any, $route: any, { isActive, isExactActive }: { isActive: boolean, isExactActive: boolean }): string | null {
if (props.exactQuery && !isEqual(route.query, $route.query))
return null
if (props.exactHash && !isEqual(route.hash, $route.hash))
return null
if (props.exact && isExactActive)
return props.navLinkActive
if (!props.exact && isActive)
return props.navLinkActive
return null
}
function resolveNavLinkInactive(route: any, $route: any, { isActive, isExactActive }: { isActive: boolean, isExactActive: boolean }): string | null {
if (props.exactQuery && !isEqual(route.query, $route.query))
return props.navLinkInactive
if (props.exactHash && !isEqual(route.hash, $route.hash))
return props.navLinkInactive
if ((!props.exact && isActive) || (props.exact && isExactActive))
return null
return props.navLinkInactive
}
function isPartiallyEqual(item1: any, item2: any) {
const diffedKeys = diff(item1, item2).reduce((filtered: Set<string>, q: any) => {
if (q.type === 'added') {
filtered.add(q.key)
}
return filtered
}, new Set<string>())
const item1Filtered = Object.fromEntries(Object.entries(item1).filter(([key]) => !diffedKeys.has(key)))
const item2Filtered = Object.fromEntries(Object.entries(item2).filter(([key]) => !diffedKeys.has(key)))
return isEqual(item1Filtered, item2Filtered)
}
function isLinkActive({ route: linkRoute, isActive, isExactActive }: any) {
if (props.active !== undefined) {
return props.active
}
if (props.exactQuery === 'partial') {
if (!isPartiallyEqual(linkRoute.query, route.query))
return false
}
else if (props.exactQuery === true) {
if (!isEqual(linkRoute.query, route.query))
return false
}
if (props.exactHash && linkRoute.hash !== route.hash) {
return false
}
if (props.exact && isExactActive) {
return true
}
if (!props.exact && isActive) {
return true
}
return false
}
return {
resolveLinkClass,
resolveNavLinkActive,
resolveNavLinkInactive,
isLinkActive,
label: props.label,
disabled: props.disabled,
}
},
})
</script>
<template>
<NuxtLink
v-slot="{ route, href, target, rel, navigate, isActive, isExactActive, isExternal }"
v-bind="$props"
custom
>
<a
v-bind="$attrs"
:href="disabled ? undefined : href"
:rel="rel ?? undefined"
:aria-disabled="disabled ? 'true' : undefined"
:target="target ?? undefined"
:class="[
{ '_link-disabled': disabled },
resolveLinkClass(route, $route, { isActive, isExactActive }),
]"
:nav-link-active="resolveNavLinkActive(route, $route, { isActive, isExactActive })"
:nav-link-inactive="resolveNavLinkInactive(route, $route, { isActive, isExactActive })"
@click="(e) => !isExternal && navigate(e)"
>
<slot :active="isLinkActive({ route, isActive, isExactActive })">
{{ label }}
</slot>
</a>
</NuxtLink>
</template>