Design guidelines
Design consistent drag and drop experiences.These guidelines provide an opinionated way to implement drag and drop experiences with Pragmatic drag and drop for Atlassian interfaces. These guidelines work within web platform design constraints for drag and drop, and aim to optimise clarity and performance. Non-Atlassian consumers are welcome to use these guidelines, or create their own visual language for drag and drop. The core package does not have any baked in design opinions, but some of the optional packages embody these design choices.
These design decisions are available in our Pragmatic drag drop Figma UI Kit (Atlassian only).
We plan on soon making this Figma public soon.
Cards, lists, and other UI can often be dragged and reordered. Design clear and consistent drag and drop experiences in Atlassian apps following these principles:
Before the drag starts
Make it clear what can be dragged
When drag and drop is a primary action for an entity, you should:
- use an always visible drag handle icon (an exception for this is when drag and drop is "implied", for example cards on a board)
- use
:hover { cursor: grab }
- Change the background color on item hover
When drag and drop is a secondary action for an entity, you should:
- use a drag handle icon that is visible on
:hover
or:focus-within
- use
:hover { cursor: grab }
after an800ms
delay (see below for how to do this with CSS) - Change the background color on item hover
An example of drag and drop as a secondary section is a menu items in a side navigation. The primary action for a menu item would be navigation, and a secondary action would be moving the menu item through drag and drop.
Please also see our accessibility guidelines which talks about our guidance for placing an action menu trigger on draggable entities to enable the same outcomes as drag and drop interactions for assistive technology users.
Which parts of an entity should be draggable?
As a starting position, if an entity is draggable (eg a card), then make the whole entity draggable. If the entity has other interactive parts (eg buttons, dropdowns), then just make the drag handle icon the draggable part of the entity.
Something to keep in mind is that making an entire entity draggable
will prevent text selection
inside that entity (platform limitation)
Drag handle icons
Our drag handle icon helps people understand what is draggable. Please use the "small"
DragHandleVerticalIcon
from @atlaskit/icon.
import DragHandleVerticalIcon from '@atlaskit/icon/core/drag-handle-vertical';
function DragHandle() {
return <DragHandleVerticalIcon size="small" />;
}
The color of the icon should match the text color of text in the draggable entity. This is usually
handled for you by default with @atlaskit/icon as it uses currentColor
by
default.
All available variants, which are explained in detail below:
Always visible drag handle
When dragging is a primary action of an entity, there should be a visible drag handle on the left hand side of the entity at all times.
You should also make your drag handle always visible if your drag handle is also your action button.
When the drag handle is the only part of an entity that is draggable, it's touch target size should
be at least 24px x 24px
in size ("spacious"
icon spacing).
import React, { Fragment, useEffect, useRef, useState } from 'react';
import { createPortal } from 'react-dom';
import invariant from 'tiny-invariant';
import DragHandleVerticalIcon from '@atlaskit/icon/core/drag-handle-vertical';
import { draggable } from '@atlaskit/pragmatic-drag-and-drop/element/adapter';
import { pointerOutsideOfPreview } from '@atlaskit/pragmatic-drag-and-drop/element/pointer-outside-of-preview';
import { setCustomNativeDragPreview } from '@atlaskit/pragmatic-drag-and-drop/element/set-custom-native-drag-preview';
// eslint-disable-next-line @atlaskit/design-system/no-emotion-primitives -- to be migrated to @atlaskit/primitives/compiled – go/akcss
import { Box, Grid, Stack, xcss } from '@atlaskit/primitives';
import { token } from '@atlaskit/tokens';
import { ActionMenu } from './shared/action-menu';
import { DragPreview } from './shared/drag-preview';
import { type DraggableState } from './shared/types';
const listItemStyles = xcss({
borderWidth: 'border.width',
borderStyle: 'solid',
borderColor: 'color.border',
padding: 'space.100',
paddingInlineStart: 'space.0',
borderRadius: 'radius.small',
backgroundColor: 'elevation.surface',
});
const draggableStyles = xcss({
':hover': {
cursor: 'grab',
backgroundColor: 'elevation.surface.hovered',
},
});
const draggingStyles = xcss({
opacity: 0.4,
});
const roundedIconStyles = xcss({ borderRadius: 'radius.small' });
export function OnlyDraggableFromDragHandle() {
const draggableRef = useRef<HTMLDivElement | null>(null);
const [state, setState] = useState<DraggableState>({ type: 'idle' });
useEffect(() => {
const element = draggableRef.current;
invariant(element);
return draggable({
element,
onGenerateDragPreview({ nativeSetDragImage }) {
setCustomNativeDragPreview({
getOffset: pointerOutsideOfPreview({
x: token('space.200', '16px'),
y: token('space.100', '8px'),
}),
nativeSetDragImage,
render({ container }) {
setState({ type: 'preview', container });
return () => setState({ type: 'dragging' });
},
});
},
onDrop() {
setState({ type: 'idle' });
},
});
}, []);
return (
<Fragment>
<Grid
alignItems="center"
templateColumns="auto 1fr auto"
xcss={[listItemStyles, state.type === 'dragging' ? draggingStyles : undefined]}
>
<Stack xcss={[draggableStyles, roundedIconStyles]} ref={draggableRef}>
<DragHandleVerticalIcon
spacing="spacious"
label="Drag list item"
color={token('color.icon')}
size="small"
/>
</Stack>
<Box>Drag handle always visible (only draggable from drag handle)</Box>
<ActionMenu />
</Grid>
{state.type === 'preview' ? createPortal(<DragPreview />, state.container) : null}
</Fragment>
);
}
Visible on hover drag handle
If dragging is a secondary action for an entity, then you can use a delayed drag handle (it is
only visible on :hover
and :focus-within
).
- This approach can be helpful when you don't want add clutter to an interface when drag and drop is a secondary action.
- Keep in mind that using this approach will make it harder for users to discover that an entity is draggable.
- The drag handle icon should use
"compact"
spacing (16px x 16px
) so you don't leave a big gap in the interface for where the drag handle icon will appear. - For best performance, use CSS to hide and show the drag handle on
:focus-within
or:hover
.
import DragHandleVerticalIcon from '@atlaskit/icon/core/drag-handle-vertical';
function DragHandle() {
return <DragHandleVerticalIcon spacing="compact" size="small" />;
}
A visible on hover drag handle can appear outside of the bounds of an element if you are pressed for room. In this scenario it's important that you make sure that a user can start the drag from the drag handle.
What you need to do:
- Make sure your drag handle is a part of the hitbox of the containing element
- Make sure your drag handle allows pointer events (
pointer-events: auto
)
Implied draggables
Some entities are considered to be "implied" to be draggable (eg cards and columns) and these entities do not require a drag handle icon - although you can still encouraged to add them. Implied draggable entities should use background color and cursor changes to make it clear which part of the entity is draggable, and there should be a strong preference to make as much of the entity draggable as possible.
Sometimes, your drag handle icon should be a button for triggering actions too (see our accessibility guidelines). In those cases, please use our drag handle icon button.
Cursor changes
Use :hover { cursor:grab; }
as a helpful signal that an entity is draggable. Only the draggable
part of an entity should have cursor:grab
. So when a draggable is only draggable from a drag
handle, only the drag handle should have cursor:grab
.
For entities where dragging is a secondary action (and you are therefore using a "visible on focus
drag handle"), you should also delay the changing of the cursor to grab
by 800ms
cursor:drag
delayed by 800ms
We strongly recommend you implement this behaviour with CSS (no JS needed!)
@keyframes change-cursor {
to {
cursor: grab;
}
}
.item:hover {
animation-name: cursor-change;
/* instant animation */
animation-duration: 0s;
/* delay cursor change */
animation-delay: 800ms;
/* keep the end state when the animation ends */
animation-fill-mode: forwards;
}
Background color changes
The draggable part of an entity should have a background color change applied when hovering over it.
Please use the appropriate hover token for the entity.
Usually for a draggable entity you will be using:
elevation.surface
as the background color.elevation.surface.hovered
on as thehover
background color on the draggable part of the entity.
For some situations it might be appropriate to use a different color pairing. Please try to use a
".background"
color token for the draggable entity, and a matching ".hovered"
token for the
hover.
Start of a drag
Make it clear what is being dragged
It is important that you give clear feedback to the user about what is being dragged.
Drag previews
A drag preview is a representation of the item being dragged. Generally a drag preview is a picture of the item being dragged around the page, and not the draggable item itself.
When an item is small and simple, the drag preview can be an exact copy of the item being dragged. If an item is larger or more complex, you should simplify the drag preview.
Simplification suggestions:
- Use a maximum of three pieces of information in a preview
- Use a maximum preview size of
280px x 280px
to prevent super low drag preview opacity on Windows
Standard
Drag previews should generally be pushed away from the users pointer by space.100
vertically, and
space.200
horizontally. Do not rotate the drag preview.
{
x: token('space.200', '16px'),
y: token('space.100', '8px'),
}

Cards
Cards are to be dragged from the point they're grabbed from (no offset).

Do not rotate card drag previews. Exception: Trello, which has a 4deg
rotation.
Cursor
Due to
web platform constraints, we
have limited control of the cursor during a drag operation. The cursor will generally be
cursor:default
during a drag operation.
The draggable item
While an item is being dragged, the original item should stay in place and dim to 40%
opacity
(opacity: 0.4
) while the drag preview is being moved around.
Multiple-item drag previews
For multi-item drag previews, use a stacked appearance with a badge indicating the number of items being dragged.
- Four or more small items such as list items should appear as a stacked preview under the first item. Less than four small items can appear separately where space allows (all items showing in the preview).
- Any more than one large item (card) should show as a stack with a badge indicating the number underneath.
Implement with native drag previews, or use the Figma kit (Atlassian only) in designs.
While dragging
Make it clear what the result will be
It should be clear to a user what the final result of the drag operation will be during the drag operation.
There are two signals you can use to indicate drop placement:
- Drop indicator (a line)
- Background color (
color.background.selected.hovered
)
Drop indicator
The drop indicator line is used to communicate relative placement (for example, before or after an item in a list)
A drop indicator line should have the following properties:
Property | Value |
---|---|
Stroke size | 2px |
Color | "color.border.selected" |
Terminal diameter | 8px ("space.100" ) |
Border radius on right hand side | none |
The terminal should bleed 4px
outwards on the left hand side of the target item. When this
bleeding is not possible due to UI constraints (such as the element appearing in a scroll container
which would cause the terminal to be cut off) then the terminal "bleeding" can be disabled and the
terminal can sit against the left edge of the entity
For stacked items, the line should appear in the middle of the gap between the items.
- Edge detection package: determines when and where the drop indicator shows based on the location of the dragged item.
- React drop indicator package: draw drop indicators
A drop indicator line should not be used when a draggable item is being dragged over a droppable area where there is no relative placement possible (for example, dropping into an empty sibling list).
Background color
A background color change is used to communicate that an item will be placed within a particular droppable area. Background color changes should be used when there are multiple possible areas of the page a draggable item can be dropped on. The droppable area that the user is currently over should have its background color changed. A background color change should also occur when a draggable item starts in a droppable area, when there are multiple possible droppable areas.
A background color change to communicate that dropping is possible should only be applied when a user can perform a drop operation. Sometimes an entity (eg a column) only allows dropping on a subset of that entity. Only the subset that allows dropping should have a background color change, and only when the user is dragging over that subset. We don't want to have a situation where a background color changes, but when the user drops, nothing happens.
Background color change animation details:
Property | Value |
---|---|
Background color | "color.background.selected.hovered" |
Easing | cubic-bezier(0.15, 1.0, 0.3, 1.0) (import {easeInOut} from '@atlaskit/motion' ) |
Duration | 350ms (import {mediumDurationMs} from '@atlaskit/motion' ) |
When draggable items can only be moved relatively within a single container, then no background color change should be used when the user is dragging something within the experience.
Nested structures
When there are multiple horizontal levels available as drop targets (for example, trees), then we encourage the use of extra visual affordances to make the levelling clearer:
- Change the background color to
'color.background.information'
for all items on the level - Add an outline to the level with the following properties:
const styles = css({
backgroundColor: token('color.background.information'),
borderRadius: token('radius.xsmall'),
outlineOffset: token('space.075'),
outlineWidth: token('border.width.outline'),
outlineStyle: 'solid',
outlineColor: token('color.border.selected'),
});
On drop
When the drag operation finishes, we want to:
- Allow users to quickly perform other operations
- Make it clear what the user achieved
Optimistically update the UI
After a drag operation completes, you should immediately update the interface to reflect the outcome of the drag operation. This is known an as "optimistic update" as the interface is updated before the change has been persisted on your backend. In addition to your optimistic update, you should fire off an asynchronous request to persist the outcome of the drag operation.
Optimistic updates might not be available for every possible interaction, but it should be an extremely strong preference to provide optimistic updates.
Flash the moved item
To improve clarity about what the user achieved, the background color of the moved item should flash once it has been moved.
.
We have implemented this flash in our flourish package for you to use with any framework.
Background color flash details:
Property | Value |
---|---|
Background color | "color.background.selected" |
Easing | cubic-bezier(0.25, 0.1, 0.25, 1.0) (not available in @atlaskit/motion ) |
Duration | 700ms (import {largeDurationMs} from '@atlaskit/motion' ) |
Provide accessible controls
All draggable items should also have the ability to achieve the same outcomes using assistive technology friendly controls.
See our accessibility guidelines
Prefer visible drag handle icons
Please make drag handle icons visible (where possible) as this is a helpful signal for people that an item is draggable.
If the item already has a more actions (…) menu, put the move actions inside of the menu. This provides a keyboard accessible way to move items that doesn’t rely on mouse clicking and dragging.
If the entity does not have any more actions (…), make the drag handle icon into a menu button. When triggered, the drag handle button opens a menu that allows the users to move the item.
Use the drag handle menu component in code or the Figma kit drag handle menu (Atlassian only) in designs.
Experience specific guidance
These are guidelines that are in addition to our standard guidance for specific experiences
Trees
For tree items, you should use our list item hitbox and our list item react drop indicator (if your experience uses react).
If you are wiring up drag and drop for our navigation-system
side navigation,
we have specific guidance for you
Drag starting
- If dragging a tree item that has children in it, you should collapse the tree item when the drag starts. A video explaining the rationale for this
- If your tree uses a pattern where tree items that have icons are replaced by chevrons on hover, when the drag starts change all the tree item icons to be chevrons. This will help make it clearer to users which part of the interface are potentially expandable.
While dragging
- Tree items can be drop targets and can support up to three operations:
"reorder-before"
,"reorder-after"
and"combine"
(see list item hitbox for more details). - The size of the operation hitboxes are controlled by the list item hitbox.
- Use the
list item react drop indicator
to visualize the
list item hitbox operation
(eg
"combine"
) that would be achieved if the user dropped the item. This will be a line above the target tree item for"reorder-above"
, a line below the target tree item for"reorder-below"
and a border around the target tree item for"combine"
. - If a user drags over a collapsed tree item with the
"combine"
operation for500ms
, then the collapsed tree item should expand (and should stay expanded even after the drag finishes) - Use our group drop indicator around the innermost group of tree items being dragged over (see tree example for illustration)
On drop
- If the tree item being dragged was expanded when the drag started, re-expand the item. If the tree item was collapsed when the drag started, keep it collapsed.
- The dragged tree item must be visible after the drag has finished. This can involve expanding any
parent tree items needed. For example: dragging
item A
ontoitem B
with a"combine"
operation can makeitem A
a child ofitem B
. Ifitem B
was not expanded at the end of the drag (ie it was not dragged over for500ms
with the"combine"
operation), thenitem B
should be expanded so thatitem A
is visible at the conclusion of the drag.
Accessible actions
- Tree items should have a standard '...' more menu to trigger movement outcomes
- For list items you generally have predefined movement actions in a dropdown menu (eg
"reorder above"
,"reorder below"
etc). However, for tree items, you add a"move"
item to the more menu dropdown, and that"move"
item should trigger a Modal. The Modal should contain a form which enables all possible movement outcomes for a tree item.
There may be some experiences where exceptions to these recommendations are are warranted, and for those you should refer to our accessibility guidelines for general principles and practices.
Dragging multiple items
How to show when more than one element is selected to be dragged.
Work in progress guidance
Cards and boards
Use color.background.selected
and color.border.selected
to show the elements that have been
selected, then change to the typical background color at 40% opacity once dragging begins. This
shows where the objects are currently, and where they'll return if no drag location is chosen.
Because cards are larger, multiple cards should be stacked under the first card in the preview. Use a badge to show how many items are being moved.
Checkboxes are highly recommended to show when one or more items are selected.
List items
Use the selected background color token to indicate which items have been selected. Use drag previews as usual, with all items being dragged in a column in the order they’d appear when dropped.
For more than three or four items, show a badge indicating the number and an implied stack beneath the top item.