Anchor
An anchor is a primitive for building custom links.Installation
| Install | yarn add @atlaskit/primitives |
|---|---|
| Source | Bitbucket.org, (opens new window) |
| npm | @atlaskit/primitives, (opens new window) |
| Bundle | unpkg.com, (opens new window) |
Anchor is a primitive for building custom links with Atlassian Design System styling, routing
support, and built-in event tracking. It renders an anchor <a> element.
Default
Anchor is unstyled besides a default underline and consistent Atlassian Design System focus styles.
If you are using the CSS reset, anchor will also inherit some global styles.
import React from 'react';
import { Anchor } from '@atlaskit/primitives/compiled/anchor';
export default function Default(): React.JSX.Element {
return <Anchor href="/components/primitives/overview">Anchor</Anchor>;
}Basic styling
Anchor can be styled further using the design system styling API using cssMap.
import React from 'react';
import { cssMap } from '@atlaskit/css';
import Image from '@atlaskit/image';
import Lozenge from '@atlaskit/lozenge/lozenge';
import { Anchor } from '@atlaskit/primitives/compiled/anchor';
import { Box } from '@atlaskit/primitives/compiled/box';
import { token } from '@atlaskit/tokens';
import ButtonIcon from '../../images/button.png';
const styles = cssMap({
anchor: {
color: token('color.link'),
backgroundColor: token('elevation.surface'),
textDecoration: 'none',
borderWidth: token('border.width'),
borderStyle: 'solid',
borderColor: token('color.border'),
borderRadius: token('radius.small'),
display: 'inline-flex',
alignItems: 'center',
gap: token('space.100'),
paddingInline: token('space.050'),
paddingBlock: token('space.025'),
'&:hover': {
backgroundColor: token('elevation.surface.hovered'),
textDecoration: 'none',
},
'&:active': {
color: token('color.link.pressed'),
backgroundColor: token('elevation.surface.pressed'),
},
'&:visited': {
color: token('color.link.visited'),
},
},
iconContainer: {
width: '16px',
display: 'flex',
},
});
export default function Basic(): React.JSX.Element {
return (
<Anchor
href="https://www.atlassian.com/software/atlas"
interactionName="atlas-link"
xcss={styles.anchor}
target="_blank"
rel="noopener noreferrer"
>
<Box xcss={styles.iconContainer}>
<Image src={ButtonIcon} alt="" />
</Box>
Evolving Button: Open beta to GA
<Lozenge appearance="success">On track</Lozenge>
</Anchor>
);
}Advanced styling
Use a combination of cssMap and other primitives for more complex designs.
import React from 'react';
import { cssMap } from '@atlaskit/css';
import Heading from '@atlaskit/heading/heading';
import type { IconProps } from '@atlaskit/icon/types';
import BlogObjectTile from '@atlaskit/object/tile/blog';
import ImprovementObjectTile from '@atlaskit/object/tile/improvement';
import PageObjectTile from '@atlaskit/object/tile/page';
import { Anchor } from '@atlaskit/primitives/compiled/anchor';
import { Box } from '@atlaskit/primitives/compiled/box';
import { Grid } from '@atlaskit/primitives/compiled/grid';
import { Inline } from '@atlaskit/primitives/compiled/inline';
import { Stack } from '@atlaskit/primitives/compiled/stack';
import { Text } from '@atlaskit/primitives/compiled/text';
import { token } from '@atlaskit/tokens';
const BlogIcon = () => <BlogObjectTile size="small" />;
const PageIcon = () => <PageObjectTile size="small" label="" />;
const ImprovementIcon = () => <ImprovementObjectTile size="small" label="" />;
const styles = cssMap({
anchor: {
color: token('color.text'),
backgroundColor: token('elevation.surface'),
paddingBlockStart: token('space.200'),
paddingInlineEnd: token('space.200'),
paddingBlockEnd: token('space.200'),
paddingInlineStart: token('space.200'),
textDecoration: 'none',
borderColor: token('color.border'),
borderStyle: 'solid',
borderWidth: token('border.width'),
borderRadius: token('radius.small'),
'&:hover': {
backgroundColor: token('elevation.surface.hovered'),
textDecoration: 'none',
},
'&:active': {
backgroundColor: token('elevation.surface.pressed'),
},
},
iconContainer: {
width: '24px',
display: 'flex',
},
grid: {
'@media (min-width: 48rem)': {
gridTemplateColumns: '1fr 1fr',
},
rowGap: token('space.100'),
columnGap: token('space.100'),
gridTemplateColumns: '1fr',
},
});
type PageLinkProps = {
href: string;
title: string;
space: string;
lastVisited: string;
icon: React.ComponentType<IconProps>;
};
const PageLink = ({ href, title, space, lastVisited, icon: Icon }: PageLinkProps) => {
return (
<Anchor href={href} xcss={styles.anchor}>
<Stack space="space.100">
<Inline space="space.150" alignBlock="center">
<Box xcss={styles.iconContainer}>
<Icon label="" />
</Box>
<Stack>
<Heading as="h3" size="small">
{title}
</Heading>
<Text color="color.text.subtle" size="small">
{space}
</Text>
</Stack>
</Inline>
<Text color="color.text.subtle" size="small">
Visited {lastVisited}
</Text>
</Stack>
</Anchor>
);
};
export default function Styled(): React.JSX.Element {
return (
<Stack space="space.200">
<Heading as="h2" size="small">
Pick up where you left off
</Heading>
<Grid xcss={styles.grid}>
<PageLink
href="/components/primitives/overview"
icon={BlogIcon}
title="Anchor primitive is now in beta!"
space="Design System Team"
lastVisited="1 hour ago"
/>
<PageLink
href="/components/primitives/overview"
icon={PageIcon}
title="Impact & release planning"
space="Design System Team"
lastVisited="1 day ago"
/>
<PageLink
href="/components/primitives/overview"
icon={PageIcon}
title="How to implement dark mode"
space="Design System Team"
lastVisited="12 May 2024"
/>
<PageLink
href="/components/primitives/overview"
icon={ImprovementIcon}
title="New Bitbucket pull requests"
space="Bitbucket Cloud"
lastVisited="10 May 2024"
/>
</Grid>
</Stack>
);
}HTML attributes
Anchor can pass all valid
anchor HTML attributes, (opens new window),
such as rel or download, to the underlying <a> element.
import React from 'react';
import { Anchor } from '@atlaskit/primitives/compiled/anchor';
export default function AnchorHTMLAttributes(): React.JSX.Element {
return (
<Anchor href="https://www.atlassian.com/" target="_blank" rel="noopener noreferrer">
Visit the Atlassian website
</Anchor>
);
}Router links
Routing libraries often supply link components enhanced with routing support. You can configure this in the AppProvider context, and anchor will automatically use it.
This example shows a configuration for React Resource Router, (opens new window), however any routing library can be used.
Using this method, anchor accepts href as a string for standard usage. For advanced usage, an
object can be passed.
Anchor will only render a router link if:
- a link component is set in the app provider
- it's not an external link (starting with
http://orhttps://) - it's not a non-HTTP-based link (e.g. emails, phone numbers, hash links etc.).
import React, { forwardRef, type Ref } from 'react';
import { Link, type LinkProps, RouteComponent, Router } from 'react-resource-router';
import AppProvider from '@atlaskit/app-provider/app-provider';
import type { RouterLinkComponentProps } from '@atlaskit/app-provider/router-link-provider';
import { Anchor } from '@atlaskit/primitives/compiled/anchor';
export type ReactResourceRouterLinkConfig = Pick<LinkProps, 'to' | 'href' | 'replace'>;
const HomePage = () => {
return (
<>
{/* Internal link: Will render a router link */}
<Anchor href="/about">Internal link</Anchor>
{/* Advanced usage */}
<Anchor<ReactResourceRouterLinkConfig>
href={{
to: '/about',
replace: true,
}}
>
Advanced link
</Anchor>
{/* External link: Will not render a router link */}
<Anchor href="https://www.atlassian.com">External link</Anchor>
{/* Non-HTTP-based: Will not render a router link */}
<Anchor href="mailto:test@example.com">Email link</Anchor>
</>
);
};
/**
* Configures a router link for the app provider.
*/
const MyRouterLinkComponent: React.ForwardRefExoticComponent<
React.PropsWithoutRef<RouterLinkComponentProps<ReactResourceRouterLinkConfig>> &
React.RefAttributes<HTMLAnchorElement>
> = forwardRef(
(
{ href, children, ...rest }: RouterLinkComponentProps<ReactResourceRouterLinkConfig>,
ref: Ref<HTMLAnchorElement>,
) => {
// A basic link by passing a string as the component's `href` prop.
if (typeof href === 'string') {
return (
<Link ref={ref} href={href} {...rest}>
{children}
</Link>
);
}
// Advanced link configuration by passing an object as the
// component's `href` prop
return (
<Link ref={ref} href={href.href} to={href.to} replace={href.replace} {...rest}>
{children}
</Link>
);
},
);
export default function RouterLinkConfiguration(): React.JSX.Element {
return (
<AppProvider routerLinkComponent={MyRouterLinkComponent}>
<Router
routes={[
{
name: 'home',
path: '',
exact: true,
component: HomePage,
},
]}
>
<RouteComponent />
</Router>
</AppProvider>
);
}Event tracking
Anchor has utilities to make tracking events easier. Events won't be captured unless listeners are set up to handle them.
Track events for any analytics provider
Anchor comes with built-in Atlaskit analytics support using the
Analytics next package, (opens new window), and
fires events for available listeners. Currently this is only available for onClick.
Events always fire on the atlaskit channel. To fire events on other channels as well, use the
provided analyticsEvent in onClick. To configure event data, use componentName (defaults to
'Anchor') and use analyticsContext to pass other metadata.
See the event data in the console.
import React, { useCallback } from 'react';
import AnalyticsListener from '@atlaskit/analytics-next/AnalyticsListener';
import type UIAnalyticsEvent from '@atlaskit/analytics-next/UIAnalyticsEvent';
import { Anchor } from '@atlaskit/primitives/compiled/anchor';
import { Inline } from '@atlaskit/primitives/compiled/inline';
export default function Analytics(): React.JSX.Element {
const handleEvent = useCallback((event: UIAnalyticsEvent, channel?: string) => {
console.log(`Channel: '${channel}'`, event);
}, []);
return (
<AnalyticsListener channel="*" onEvent={handleEvent}>
<Inline space="space.100">
<Anchor href="/components/primitives/overview" target="_blank">
Default
</Anchor>
<Anchor
href="/components/primitives/overview"
target="_blank"
onClick={(_, analyticsEvent) => {
analyticsEvent.fire('my-channel');
}}
>
Fires on "my-channel"
</Anchor>
<Anchor
href="/components/primitives/overview"
target="_blank"
componentName="MyButton"
analyticsContext={{
color: 'blue',
someId: 937458,
}}
>
Customized event data
</Anchor>
</Inline>
</AnalyticsListener>
);
}Track events for Atlassian internal services
GASv3 analytics
The Atlassian analytics bridge makes Atlaskit analytics events compatible with GASv3 (Global
Analytics Service). This can also inject an actionSubjectId to the event if required.
See the event data in the console.
import React, { useCallback } from 'react';
import AnalyticsListener from '@atlaskit/analytics-next/AnalyticsListener';
import type UIAnalyticsEvent from '@atlaskit/analytics-next/UIAnalyticsEvent';
import { Anchor } from '@atlaskit/primitives/compiled/anchor';
import {
ANALYTICS_BRIDGE_CHANNEL,
extractAWCDataFromEvent,
fireUIAnalytics,
} from '@atlassian/analytics-bridge';
export default function AnalyticsGASv3(): React.JSX.Element {
const handleEvent = useCallback((event: UIAnalyticsEvent, channel?: string) => {
console.log(`Channel: '${channel}'`, extractAWCDataFromEvent(event));
}, []);
const handleClick = useCallback(
(_: React.MouseEvent<HTMLAnchorElement, MouseEvent>, analyticsEvent: UIAnalyticsEvent) => {
fireUIAnalytics(analyticsEvent, 'theActionSubjectId');
},
[],
);
return (
<AnalyticsListener channel={ANALYTICS_BRIDGE_CHANNEL} onEvent={handleEvent}>
<Anchor
href="/components/primitives/overview"
target="_blank"
onClick={handleClick}
analyticsContext={{
attributes: {
color: 'blue',
someId: 937458,
},
}}
>
Fire GASv3 compatible event
</Anchor>
</AnalyticsListener>
);
}React UFO press interactions
By default, anchor fires
React UFO (Unified Frontend Observability) press interactions, (opens new window)
for available listeners. This helps Atlassian measure performance and reliability. You can provide
more detail using the interactionName prop.
import React from 'react';
import { cssMap } from '@atlaskit/css';
import __noop from '@atlaskit/ds-lib/noop';
import { FlagsProvider } from '@atlaskit/flag/flags-provider';
import { useFlags } from '@atlaskit/flag/use-flags';
import Heading from '@atlaskit/heading/heading';
import InformationIcon from '@atlaskit/icon/core/status-information';
import Image from '@atlaskit/image';
import InteractionContext from '@atlaskit/interaction-context';
import { Anchor } from '@atlaskit/primitives/compiled/anchor';
import { Box } from '@atlaskit/primitives/compiled/box';
import { Flex } from '@atlaskit/primitives/compiled/flex';
import { Inline } from '@atlaskit/primitives/compiled/inline';
import { Stack } from '@atlaskit/primitives/compiled/stack';
import { token } from '@atlaskit/tokens';
import ButtonIcon from '../../images/button.png';
import ThemesIcon from '../../images/themes.png';
import WatermelonIcon from '../../images/watermelon.png';
const iconSpacingStyles = cssMap({
space050: {
paddingBlock: token('space.050'),
paddingInline: token('space.050'),
},
});
const styles = cssMap({
anchor: {
color: token('color.text'),
textDecoration: 'none',
'&:hover': {
color: token('color.text'),
textDecoration: 'underline',
},
'&:active': {
textDecoration: 'none',
},
'&:visited': {
color: token('color.link.visited'),
},
},
iconContainer: {
width: '24px',
display: 'flex',
},
});
type ProjectLinkProps = {
children: string;
icon: string;
id: string;
};
const ProjectLink = ({ children, icon, id }: ProjectLinkProps) => {
return (
<Anchor href="#" xcss={styles.anchor} interactionName={`anchor-${id}`}>
<Inline space="space.150" alignBlock="center">
<Box xcss={styles.iconContainer}>
<Image src={icon} alt="" />
</Box>
{children}
</Inline>
</Anchor>
);
};
const Projects = () => {
const { showFlag } = useFlags();
return (
<InteractionContext.Provider
value={{
hold: __noop,
tracePress: (name) => {
console.log('Traced a press!', name);
showFlag({
title: `Traced a press!`,
description: name,
icon: (
<Flex xcss={iconSpacingStyles.space050}>
<InformationIcon label="Info" color={token('color.icon.information')} />
</Flex>
),
isAutoDismiss: true,
});
},
}}
>
<Stack space="space.200">
<Heading as="h2" size="small">
Your projects
</Heading>
<Stack space="space.100">
<ProjectLink icon={ButtonIcon} id="evolving-button">
Evolving Button: Open beta to GA
</ProjectLink>
<ProjectLink icon={ThemesIcon} id="increased-contrast-themes">
Increased contrast themes
</ProjectLink>
<ProjectLink icon={WatermelonIcon} id="typography">
ADS Typography
</ProjectLink>
</Stack>
</Stack>
</InteractionContext.Provider>
);
};
export default function PressTracing(): React.JSX.Element {
return (
<FlagsProvider>
<Projects />
</FlagsProvider>
);
}