Link
linkuiStyled anchor with router integration. Renders an <a> for href, a router-link for to, and handles external links with target/rel. Supports underline variants (always/hover/none), color variants (default/primary/muted), disabled state, left/right icon slots, size variants, and asChild composition.
Installation
$pnpm dlx shadcn@latest add https://uipkge.dev/r/lit/link.json$npx shadcn@latest add https://uipkge.dev/r/lit/link.json$yarn dlx shadcn@latest add https://uipkge.dev/r/lit/link.json$bunx shadcn@latest add https://uipkge.dev/r/lit/link.jsonManifest:
https://uipkge.dev/r/lit/link.jsonInstalls to:src/uip/components/link/Examples
Loading interactive previews…
Loading interactive previews…
Props
| Name | Type / Values | Default | Required |
|---|---|---|---|
href | string | — | optional |
to | string | — | optional |
underline | 'none''always''hover' | hover | optional |
color | 'default''primary''muted' | primary | optional |
size | 'sm''default''lg' | default | optional |
external | boolean | — | optional |
disabled | boolean | false | optional |
target | string | — | optional |
rel | string | — | optional |
Schema
Type aliases from this item's source — use them to shape the data you pass in.
HTMLElementTagNameMapinterface HTMLElementTagNameMap {
'uip-link': UipLink
}
}npm dependencies
Includes
Files installed (2)
src/uip/components/link/link.ts3 kB
import { LitElement, css, html, nothing } from 'lit' import { cn } from '../../lib/utils' import { tailwind } from '../../lib/styles' import { ThemeController } from '../../lib/theme' import { linkVariants, type LinkVariants } from './link.variants' type Underline = NonNullable<LinkVariants['underline']> type Color = NonNullable<LinkVariants['color']> type Size = NonNullable<LinkVariants['size']> /** * <uip-link> — styled navigational link. */ export class UipLink extends LitElement { static styles = [ tailwind, css` :host { display: inline-flex; } `, ] static properties = { href: { reflect: true }, to: { reflect: true }, underline: { reflect: true }, color: { reflect: true }, size: { reflect: true }, external: { type: Boolean, reflect: true }, disabled: { type: Boolean, reflect: true }, target: { reflect: true }, rel: { reflect: true }, } href?: string to?: string underline: Underline = 'hover' color: Color = 'primary' size: Size = 'default' external?: boolean disabled = false target?: string rel?: string constructor() { super() new ThemeController(this) } connectedCallback() { super.connectedCallback() this.setAttribute('data-uipkge', '') this.setAttribute('data-slot', 'link') } willUpdate() { this.setAttribute('data-underline', this.underline) this.setAttribute('data-color', this.color) this.setAttribute('data-size', this.size) if (this.disabled) { this.setAttribute('data-disabled', '') } else { this.removeAttribute('data-disabled') } } private onClick(e: MouseEvent) { if (this.disabled) { e.preventDefault() e.stopPropagation() } } render() { const isExternal = !this.disabled && (this.external !== undefined ? this.external : typeof this.href === 'string' && /^https?:\/\//.test(this.href)) const target = this.target ?? (isExternal ? '_blank' : undefined) const rel = this.rel ?? (isExternal ? 'noopener noreferrer' : undefined) const resolvedHref = this.disabled ? undefined : (this.to ?? this.href) return html` <a part="base" data-slot="link" href=${resolvedHref ?? nothing} target=${target ?? nothing} rel=${rel ?? nothing} aria-disabled=${this.disabled ? 'true' : nothing} tabindex=${this.disabled ? -1 : nothing} class=${cn( linkVariants({ underline: this.underline, color: this.color, size: this.size }), this.disabled && 'pointer-events-none opacity-50', )} @click=${this.onClick} > <slot name="left" class="[&::slotted(svg)]:size-4 [&::slotted(svg)]:shrink-0"></slot> <slot class="[&::slotted(svg)]:size-4 [&::slotted(svg)]:shrink-0"></slot> <slot name="right" class="[&::slotted(svg)]:size-4 [&::slotted(svg)]:shrink-0"></slot> </a> ` } } customElements.get('uip-link') || customElements.define('uip-link', UipLink) declare global { interface HTMLElementTagNameMap { 'uip-link': UipLink } }src/uip/components/link/link.variants.ts1 kB
import type { VariantProps } from 'class-variance-authority' import { cva } from 'class-variance-authority' export const linkVariants = cva( "inline-flex items-center gap-1.5 font-medium transition-colors duration-200 outline-none focus-visible:ring-ring/50 focus-visible:ring-[3px] rounded-sm [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", { variants: { underline: { none: 'no-underline', always: 'underline underline-offset-4', hover: 'no-underline hover:underline hover:underline-offset-4', }, color: { default: 'text-foreground hover:text-foreground/80', primary: 'text-primary hover:text-primary/80', muted: 'text-muted-foreground hover:text-foreground', }, size: { sm: 'text-xs gap-1', default: 'text-sm', lg: 'text-base', }, }, defaultVariants: { underline: 'hover', color: 'primary', size: 'default', }, }, ) export type LinkVariants = VariantProps<typeof linkVariants>
Raw manifest:https://uipkge.dev/r/lit/link.json