BtnLink
Give a destination the emphasis it deserves. BtnLink combines the visual language of a button with the familiar behavior of a real link.
BtnLinkUse BtnLink to navigate to a page, a section, or an external destination. For an action such as saving, submitting, or opening a dialog, use Btn.
Playground
Change the destination, target, relationship, and appearance. The preview is a working link: its initial destination is a section further down this page. The line beneath it shows the effective navigation attributes. Choose an icon before enabling icon-only mode.
<RegorApp id="link-playground" src="./link-playground.ts"/> import {
type BtnIconPosition,
type BtnSize,
type ComponentVariant,
defineButtonComponents,
defineFlexComponents,
defineFormComponents,
defineFormInputField,
defineFormSelectField,
defineGridComponents,
defineIconComponents,
type FormSelectOption,
} from '@purestack/ts-components'
import { SEMANTIC_TONES, type SemanticTone } from '@purestack/ts-style'
import {
lucide_chevron_down,
tabler_arrow_right,
tabler_arrow_up_right,
} from '@purestack/ts-svg-icons'
import {
type ComputedRef,
computed,
createApp,
defineComponent,
html,
type Ref,
ref,
} from 'regor'
export interface LinkPlayground {
tone: Ref<SemanticTone>
variant: Ref<ComponentVariant>
size: Ref<BtnSize>
href: Ref<string>
target: Ref<string>
rel: Ref<string>
icon: Ref<string>
position: Ref<BtnIconPosition>
iconOnly: Ref<boolean>
linkLabel: Ref<string>
resolvedIcon: ComputedRef<string>
resolvedIconOnly: ComputedRef<boolean>
effectiveRel: ComputedRef<string>
tones: FormSelectOption[]
variants: FormSelectOption[]
sizes: FormSelectOption[]
destinations: FormSelectOption[]
targets: FormSelectOption[]
relationships: FormSelectOption[]
icons: FormSelectOption[]
positions: FormSelectOption[]
reset: () => void
}
const linkPlaygroundTemplate = html`<Flex direction="column">
<Flex align="center" justify="center" class="p-4">
<BtnLink
:href="href"
:target="target"
:rel="rel"
:tone="tone"
:variant="variant"
:size="size"
:icon="resolvedIcon"
:iconPosition="position"
:iconOnly="resolvedIconOnly"
:ariaLabel="linkLabel"
>{{ linkLabel }}</BtnLink>
</Flex>
<p role="status" class="m-0">Destination: <code>{{ href }}</code> · Target: <code>{{ target }}</code> · Rel: <code>{{ effectiveRel }}</code></p>
<Grid columns="1" columnsSm="2" columnsLg="3">
<FormInputField id="link-label" label="Label / accessible name" :model="linkLabel"/>
<FormSelectField id="link-href" label="Destination" :model="href" :options="destinations"/>
<FormSelectField id="link-target" label="Target" :model="target" :options="targets"/>
<FormSelectField id="link-rel" label="Relationship" :model="rel" :options="relationships"/>
<FormSelectField id="link-tone" label="Tone" :model="tone" :options="tones"/>
<FormSelectField id="link-variant" label="Variant" :model="variant" :options="variants"/>
<FormSelectField id="link-size" label="Size" :model="size" :options="sizes"/>
<FormSelectField id="link-icon" label="Icon" :model="icon" :options="icons"/>
<FormSelectField id="link-position" label="Icon position" :model="position" :options="positions"/>
</Grid>
<Flex align="center" wrap="true">
<FormCheck id="link-icon-only" label="Icon only" :checked="iconOnly"/>
<Btn variant="link" @click="reset">Reset playground</Btn>
</Flex>
</Flex>`
function createLinkPlayground(): LinkPlayground {
const tone = ref<SemanticTone>('accent')
const variant = ref<ComponentVariant>('solid')
const size = ref<BtnSize>('md')
const href = ref('#link-destination')
const target = ref('_self')
const rel = ref('')
const icon = ref('tabler:arrow-right')
const position = ref<BtnIconPosition>('end')
const iconOnly = ref(false)
const linkLabel = ref('Explore the destination')
const resolvedIcon = computed(() => (icon() === 'none' ? '' : icon()))
const resolvedIconOnly = computed(() => iconOnly() && resolvedIcon() !== '')
const effectiveRel = computed(
() => rel() || (target() === '_blank' ? 'noopener noreferrer' : 'not set'),
)
const options = (values: string[]): FormSelectOption[] =>
values.map((value) => ({ label: value, value }))
return {
tone,
variant,
size,
href,
target,
rel,
icon,
position,
iconOnly,
linkLabel,
resolvedIcon,
resolvedIconOnly,
effectiveRel,
tones: options(SEMANTIC_TONES),
variants: options([
'solid',
'surface',
'surfaceAlt',
'spotlight',
'glass',
'flat',
'flatAlt',
'flatSolid',
'outlineFill',
'outline',
'subtle',
'subtleBtn',
'link',
'sheen',
'underline',
'rail',
'bracket',
'none',
]),
sizes: options(['sm', 'md', 'lg']),
destinations: [
{ label: 'This page’s destination', value: '#link-destination' },
{ label: 'Buttons documentation', value: '/components/actions/buttons/' },
{ label: 'Badge documentation', value: '/components/actions/badge/' },
],
targets: [
{ label: 'Current tab (_self)', value: '_self' },
{ label: 'New tab (_blank)', value: '_blank' },
],
relationships: [
{ label: 'Automatic', value: '' },
{
label: 'nofollow + noopener + noreferrer',
value: 'nofollow noopener noreferrer',
},
],
icons: options(['none', 'tabler:arrow-right', 'tabler:arrow-up-right']),
positions: options(['start', 'end']),
reset: () => {
icon('tabler:arrow-right')
iconOnly(false)
position('end')
tone('accent')
variant('solid')
size('md')
href('#link-destination')
target('_self')
rel('')
linkLabel('Explore the destination')
},
}
}
const icons: Record<string, string> = {
'lucide:chevron-down': lucide_chevron_down,
'tabler:arrow-right': tabler_arrow_right,
'tabler:arrow-up-right': tabler_arrow_up_right,
}
const component = defineComponent<LinkPlayground>(linkPlaygroundTemplate, {
context: createLinkPlayground,
})
createApp(
{
components: {
LinkPlayground: component,
...defineButtonComponents(),
...defineFlexComponents(),
...defineGridComponents(),
...defineFormComponents(),
...defineFormInputField(),
...defineFormSelectField(),
...defineIconComponents((name) => {
if (!icons[name]) throw new Error(`Icon is not registered: ${name}`)
return icons[name]
}),
},
},
{ selector: 'app#link-playground', template: html`<LinkPlayground/>` },
) Destinations
One component, three familiar journeys. Internal URLs use the site's public URL resolver during server rendering. Fragment links stay on the page. External links can open a new browsing context with target="_blank".
<Flex wrap="true" align="center">
<BtnLink href="/components/actions/buttons/" tone="accent">Read the button guide</BtnLink>
<BtnLink href="#link-destination" variant="outline">Jump to the destination</BtnLink>
<BtnLink
href="https://github.com/PureStackStudio/PureStack"
target="_blank"
variant="surface"
icon="tabler:arrow-up-right"
iconPosition="end"
>GitHub (opens in a new tab)</BtnLink>
</Flex> Target and relationship
When target is _blank and rel is omitted or empty, BtnLink supplies noopener noreferrer. An explicit, non-empty rel replaces that default. Include the relationship tokens you need in the explicit value.
<Flex wrap="true" align="center">
<BtnLink
href="https://github.com/PureStackStudio/PureStack"
target="_blank"
variant="outline"
>Repository (new tab)</BtnLink>
<BtnLink
href="https://github.com/PureStackStudio/PureStack"
target="_blank"
rel="nofollow noopener noreferrer"
variant="surface"
>Repository with nofollow (new tab)</BtnLink>
</Flex> Variants
Thirteen treatments share the same link semantics. Use a strong treatment for the primary destination and quieter ones for supporting navigation. Hover or focus each link to explore its state styling.
<Flex wrap="true" align="center">
<BtnLink href="#link-destination" tone="accent" variant="solid">solid</BtnLink>
<BtnLink href="#link-destination" tone="accent" variant="surface">surface</BtnLink>
<BtnLink href="#link-destination" tone="accent" variant="surfaceAlt">surfaceAlt</BtnLink>
<BtnLink href="#link-destination" tone="accent" variant="spotlight">spotlight</BtnLink>
<BtnLink href="#link-destination" tone="accent" variant="glass">glass</BtnLink>
<BtnLink href="#link-destination" tone="accent" variant="flat">flat</BtnLink>
<BtnLink href="#link-destination" tone="accent" variant="flatAlt">flatAlt</BtnLink>
<BtnLink href="#link-destination" tone="accent" variant="flatSolid">flatSolid</BtnLink>
<BtnLink href="#link-destination" tone="accent" variant="outlineFill">outlineFill</BtnLink>
<BtnLink href="#link-destination" tone="accent" variant="outline">outline</BtnLink>
<BtnLink href="#link-destination" tone="accent" variant="subtle">subtle</BtnLink>
<BtnLink href="#link-destination" tone="accent" variant="subtleBtn">subtleBtn</BtnLink>
<BtnLink href="#link-destination" tone="accent" variant="link">link</BtnLink>
<BtnLink href="#link-destination" tone="accent" variant="sheen">sheen</BtnLink>
<BtnLink href="#link-destination" tone="accent" variant="underline">underline</BtnLink>
<BtnLink href="#link-destination" tone="accent" variant="rail">rail</BtnLink>
<BtnLink href="#link-destination" tone="accent" variant="bracket">bracket</BtnLink>
<BtnLink href="#link-destination" tone="accent" variant="none">none</BtnLink>
</Flex> Tones
Ten semantic tones express intent through the active theme. Pick a tone for its meaning; keep the destination clear in the link text.
<Flex wrap="true" align="center">
<BtnLink href="#link-destination" tone="neutral">neutral</BtnLink>
<BtnLink href="#link-destination" tone="accent">accent</BtnLink>
<BtnLink href="#link-destination" tone="feature">feature</BtnLink>
<BtnLink href="#link-destination" tone="secondary">secondary</BtnLink>
<BtnLink href="#link-destination" tone="custom">custom</BtnLink>
<BtnLink href="#link-destination" tone="ghost">ghost</BtnLink>
<BtnLink href="#link-destination" tone="info">info</BtnLink>
<BtnLink href="#link-destination" tone="success">success</BtnLink>
<BtnLink href="#link-destination" tone="warning">warning</BtnLink>
<BtnLink href="#link-destination" tone="danger">danger</BtnLink>
</Flex> Sizes and icons
Choose sm, md, or lg. An icon can lead or follow the label. Icon-only links need an ariaLabel that explains the destination; the visible slot is hidden in this mode.
<Flex direction="column" align="start">
<Flex wrap="true" align="center">
<BtnLink href="#link-destination" size="sm" tone="accent">Small link</BtnLink>
<BtnLink href="#link-destination" size="md" tone="accent">Medium link</BtnLink>
<BtnLink href="#link-destination" size="lg" tone="accent">Large link</BtnLink>
</Flex>
<Flex wrap="true" align="center">
<BtnLink href="#link-destination" variant="surface" icon="tabler:arrow-right">Leading icon</BtnLink>
<BtnLink href="#link-destination" variant="outline" icon="tabler:arrow-right" iconPosition="end">Trailing icon</BtnLink>
<BtnLink
href="#link-destination"
tone="accent"
icon="tabler:arrow-right"
:iconOnly="true"
ariaLabel="Jump to the link destination"
/>
</Flex>
</Flex> Link destination
You have reached the target used by the same-page examples. The URL fragment changes through native anchor navigation. No click handler or application router is needed.
Back to the playgroundAccessibility
- Give every link a meaningful destination and a name that makes sense in a list of links. Prefer “Read the button guide” to “Click here”.
- A link with an
hrefis reachable with Tab and activated with Enter. Browser features such as copying its address and opening it in another tab remain available. - Let visible text name the link. Supply
ariaLabelfor an icon-only link, or when the visible text needs more context. - Signal when a destination opens in a new tab. The examples include this information in the label.
- Add
aria-current="page"yourself for the current page in a navigation region. BtnLink does not detect the current route. - BtnLink has no disabled state. If navigation is unavailable, present explanatory text or omit the link until its destination is available.
API reference
<BtnLink href="/guide/" /> A native <a> with ten public props, one default slot, and the shared button appearance. Static links need no Regor application.
Props accept values or Regor refs. For reactive expressions, pass a computed ref as shown in the playground. Register defineButtonComponents() when mounting a browser app; register defineIconComponents() with the icons used by that app.
Navigation
href
string - Default
not set
The destination of the anchor. Accepts page paths, absolute URLs, and fragments. During server rendering, PureStack resolves site paths through the current public URL context.
/components/actions/buttons/#playgroundhttps://example.comAn omitted or empty value produces an anchor without a destination. Supply a real URL for usable navigation.
<BtnLink href="/components/actions/buttons/">Read the button guide</BtnLink> target
string - Default
not set
Selects the browsing context. Without a target, the browser uses its normal navigation behavior. A named frame is also supported.
_self_blank_parent_topframe name<BtnLink href="https://github.com/PureStackStudio/PureStack" target="_blank">
PureStack on GitHub (new tab)
</BtnLink> rel
string - Default
automatic for _blank
Space-separated relationship tokens. An omitted or empty value becomes noopener noreferrer for target="_blank"; otherwise the attribute is omitted.
An explicit value replaces the automatic value; tokens are not merged.
<BtnLink href="https://github.com/PureStackStudio/PureStack" target="_blank" rel="nofollow noopener noreferrer">
Repository (new tab)
</BtnLink> Appearance
tone
SemanticTone - Default
inherited
variant
ComponentVariant - Default
solid
size
BtnSize - Default
md (base size)
Icons and labels
icon
string - Default
not set
An icon name from the configured registry. Empty or omitted values render no icon. Browser applications must register the icons they use.
<BtnLink href="#playground" icon="tabler:arrow-right">Open playground</BtnLink> iconPosition
BtnIconPosition - Default
start
iconOnly
boolean - Default
false
Hides the visible label and uses a compact icon treatment. Supply both an icon and an accessible name.
<BtnLink href="#playground" icon="tabler:arrow-right" :iconOnly="true" ariaLabel="Open the link playground"/> ariaLabel
string - Default
not set
Sets the anchor's aria-label. Omit it when visible text already names the destination. When provided, it becomes the accessible name.
Keep the visible label in the accessible name when both are present, so speech navigation can identify the link.
Slots and native attributes
Default slot
The visible link label. Accepts text and inline markup; keep other interactive controls outside the link. Hidden when iconOnly is true.
Native attributes
Attributes such as id, class, title, download, and aria-current reach the anchor. Native browser rules still apply to downloads and browsing contexts.
Events
Native anchor events are available in Regor apps. Navigation needs no handler. If you add @click, preserve the browser's default behavior unless your application intentionally handles the navigation.
<BtnLink href="/components/actions/btn-link/" variant="link" aria-current="page">BtnLink</BtnLink> For actions, see Buttons. To arrange related links and actions, see BtnGroup.