Overlay

Overlays are floating labels that are meant to clarify, display additional context information, or help you to use specific content. You can also use them for analysing different chart types, infographics, tags or tiles (see overlays in use section).

´ Loading interactive demo...
<span [dtOverlay]="overlay" style="cursor: pointer;" [dtOverlayConfig]="config" > Hover me </span> <ng-template #overlay> <p>Overlay content</p> </ng-template> export class OverlayDefaultExample { config: DtOverlayConfig = { pinnable: true, originY: 'center', }; } <span [dtOverlay]="overlay" style="cursor: pointer;" [dtOverlayConfig]="config" > Hover me </span> <ng-template #overlay> <p>Overlay content</p> </ng-template> export class OverlayDefaultExample { config: DtOverlayConfig = { pinnable: true, originY: 'center', }; }

Styleguide

Overlay basic styles

Measurements

x: 16px
y: 12px
z: 8 px

Line

line: 1px dotted
color: $gray-300
min-width: 20px

Label

font-family: Bernina Sans Regular
font-size: 12px
color: $gray-500

Entity name, value

font-family: Bernina Sans Regular
font-size: 14px
color: $gray-700
Long entity names or links can be abbreviated with three dots

Variants

It may be necessary to include interactive elements such as links or buttons. If this is the case, overlays should remain pinned to be able to use these elements. Normal overlays will always change to interactive overlays if used on touch. Interactive overlays always contain a nested close button.

´ Loading interactive demo...
<div class="timeline" tabindex="-1" #timeline [dtOverlay]="overlay" [dtOverlayConfig]="config" (mouseover)="_onMouseOver()" (mouseout)="_onMouseOut()" > <ng-content></ng-content> </div> <ng-template #overlay> <span>{{ time | date: 'mm:ss' }}</span> </ng-template> export class TimelineComponent { @ViewChild('timeline', { read: ElementRef, static: true }) timeline: ElementRef; config: DtOverlayConfig = { movementConstraint: 'xAxis', originY: 'edge', }; time: Date; duration = 90; private _moveSub = Subscription.EMPTY; constructor(public elementRef: ElementRef, private _ngZone: NgZone) { const date = new Date(); date.setMinutes(0); date.setSeconds(0); this.time = date; } _onMouseOver(): void { this._ngZone.runOutsideAngular(() => { this._moveSub = fromEvent(this.elementRef.nativeElement, 'mousemove') .pipe( map((ev: MouseEvent) => ev.offsetX), distinctUntilChanged(), map(offset => this._calculateTime(offset)), ) .subscribe(offset => { this._ngZone.run(() => { const minutes = Math.floor(offset / 60); const seconds = Math.floor(offset - minutes * 60); const date = new Date(); date.setSeconds(seconds); date.setMinutes(minutes); this.time = date; }); }); }); } _onMouseOut(): void { this._moveSub.unsubscribe(); } private _calculateTime(offset: number): number { const boundingBox = this.elementRef.nativeElement.getBoundingClientRect(); const secPerPixel = this.duration / boundingBox.width; return Math.floor(secPerPixel * offset); } } @Component({ selector: 'dt-timeline-point', template: ` <div class="point" [dtOverlay]="overlay" [dtOverlayConfig]="config" [ngStyle]="{ transform: _translation }" ></div> <ng-template #overlay> <p>Page Load: page/orange.jsf</p> <button dt-button>View highlight</button> </ng-template> `, styles: [ ` .point { border-radius: 50%; width: 20px; height: 20px; display: inline-block; background-color: #c396e0; border: 1px solid #ffffff; text-align: center; line-height: 20px; color: white; font-size: 14px; position: absolute; top: 0px; } p { margin-top: 0; } `, ], }) export class TimelinePointComponent { config: DtOverlayConfig = { pinnable: true, }; @Input() id: number; @Input() get position(): number { return this._position; } set position(value: number) { this._position = value; } private _position = 0; get _translation(): string { if (this._timeline) { const timelineRect = this._timeline.elementRef.nativeElement.getBoundingClientRect() as ClientRect; const translation = Math.max( 0, (timelineRect.width / 100) * this.position - 20, ); return `translate(${translation}px)`; } return 'translate(0)'; } constructor(@Optional() @SkipSelf() private _timeline: TimelineComponent) {} } @Component({ moduleId: module.id, selector: 'demo-component', template: ` <dt-timeline> <dt-timeline-point id="1" position="10"></dt-timeline-point> <dt-timeline-point id="2" position="30"></dt-timeline-point> <dt-timeline-point id="3" position="40"></dt-timeline-point> </dt-timeline> `, }) export class OverlayTimelineExample {} <div class="timeline" tabindex="-1" #timeline [dtOverlay]="overlay" [dtOverlayConfig]="config" (mouseover)="_onMouseOver()" (mouseout)="_onMouseOut()" > <ng-content></ng-content> </div> <ng-template #overlay> <span>{{ time | date: 'mm:ss' }}</span> </ng-template> export class TimelineComponent { @ViewChild('timeline', { read: ElementRef, static: true }) timeline: ElementRef; config: DtOverlayConfig = { movementConstraint: 'xAxis', originY: 'edge', }; time: Date; duration = 90; private _moveSub = Subscription.EMPTY; constructor(public elementRef: ElementRef, private _ngZone: NgZone) { const date = new Date(); date.setMinutes(0); date.setSeconds(0); this.time = date; } _onMouseOver(): void { this._ngZone.runOutsideAngular(() => { this._moveSub = fromEvent(this.elementRef.nativeElement, 'mousemove') .pipe( map((ev: MouseEvent) => ev.offsetX), distinctUntilChanged(), map(offset => this._calculateTime(offset)), ) .subscribe(offset => { this._ngZone.run(() => { const minutes = Math.floor(offset / 60); const seconds = Math.floor(offset - minutes * 60); const date = new Date(); date.setSeconds(seconds); date.setMinutes(minutes); this.time = date; }); }); }); } _onMouseOut(): void { this._moveSub.unsubscribe(); } private _calculateTime(offset: number): number { const boundingBox = this.elementRef.nativeElement.getBoundingClientRect(); const secPerPixel = this.duration / boundingBox.width; return Math.floor(secPerPixel * offset); } } @Component({ selector: 'dt-timeline-point', template: ` <div class="point" [dtOverlay]="overlay" [dtOverlayConfig]="config" [ngStyle]="{ transform: _translation }" ></div> <ng-template #overlay> <p>Page Load: page/orange.jsf</p> <button dt-button>View highlight</button> </ng-template> `, styles: [ ` .point { border-radius: 50%; width: 20px; height: 20px; display: inline-block; background-color: #c396e0; border: 1px solid #ffffff; text-align: center; line-height: 20px; color: white; font-size: 14px; position: absolute; top: 0px; } p { margin-top: 0; } `, ], }) export class TimelinePointComponent { config: DtOverlayConfig = { pinnable: true, }; @Input() id: number; @Input() get position(): number { return this._position; } set position(value: number) { this._position = value; } private _position = 0; get _translation(): string { if (this._timeline) { const timelineRect = this._timeline.elementRef.nativeElement.getBoundingClientRect() as ClientRect; const translation = Math.max( 0, (timelineRect.width / 100) * this.position - 20, ); return `translate(${translation}px)`; } return 'translate(0)'; } constructor(@Optional() @SkipSelf() private _timeline: TimelineComponent) {} } @Component({ moduleId: module.id, selector: 'demo-component', template: ` <dt-timeline> <dt-timeline-point id="1" position="10"></dt-timeline-point> <dt-timeline-point id="2" position="30"></dt-timeline-point> <dt-timeline-point id="3" position="40"></dt-timeline-point> </dt-timeline> `, }) export class OverlayTimelineExample {}

Behavior

Overlays appear on hover, focus, tap or click. They disappear on hover out, lost focus, tap, or click. As a general rule, only one overlay can be visible at a time. There may be exceptions for special use cases e.g. heat fields.

To use the interactive elements it's necessary to pin overlays. This can be triggered either by click, focus, or tap. Pinning can be released by click, lost focus, or tap.

As a general rule the position of an overlay is always relative to the mouse pointer. By default, the overlay should appear at the lower right corner of the cursor. If the available space can't accommodate the overlay, it should appear on the left. The same principle applies to the top corners. In special use cases, the position of the overlay can also be relative to the overlay-connected element you hover over.

Animation

To avoid that the overlay pops up when you pass triggering elements, the overlay should have a small delay of 100ms. The fade-in and fade-out animation should both last 150ms.

Overlay animation

Responsive behaviour

As a general rule overlays should behave responsively. Overlays should always fit the available screensize and the content should reflow accordingly. When in doubt please contact an UX Teammember.

Overlay responsive behaviour

Do's and don'ts

Regular overlays should not:

  • contain any interactive elements like links, buttons, drop-downs, scrollbars etc.
  • influence the mouse cursor (e.g. use "hand" cursor instead of normal pointer)

Interactive overlays should not:

  • contain scrollbars, drop-downs, expandable sections, tabs etc.
  • be used together with components where clicking and tapping is already reserved for other interactions (e.g. click to pin hairline within a chart), because in this case it's not possible to pin the interactive overlay.

Overlays in use

Overlays only appear in combination with other components. Here are some examples.

Table

The information visible in the table overlay can also be found within an expanded table row. The information belonging to the hovered column will be highlighted within the overlay.

Table overlay

Infographics

Overlay example with infographic

Tags

Overlay example with tags

Tiles

Overlay example with tile

Overlay content

The content in overlays can vary depending on the usage. Please use our Angular components as content elements.

Overlay basic
x: 16px
Overlay variants icon

General

x: 16px
y: 12px
z: 4px

Icon

height: 20px
width: 20px
Overlay variants microbarchart

General

z: 8px

Microbarchart

height: 8px
min-width: 160px

Overlays can also contain tables. Table rows in overlays have a height of 28px.

Overlay variants table
x: 16px
y: 12px
Overlay interactive variant 2
y: 12px
x: 16px
Overlay interactive chart interaction

The overlay component is used to display additional context information or to analyze charts, tags or tiles. The overlay's content should be reduced to a minimal amount of necessary components to display further information and actions.

There are two ways to create overlays: using the DtOverlay service or the DtOverlayTrigger directive. The service can be used to apply overlays programatically or to pass components into the overlay. The directive can be used inside the template with a templateRef as the content of the overlay. The following basic example uses the DtOverlay service and passes an optional configuration (find details below) to the overlay.

´ Loading interactive demo...
<span [dtOverlay]="overlay" style="cursor: pointer;" [dtOverlayConfig]="config" > Hover me </span> <ng-template #overlay> <p>Overlay content</p> </ng-template> export class OverlayDefaultExample { config: DtOverlayConfig = { pinnable: true, originY: 'center', }; } <span [dtOverlay]="overlay" style="cursor: pointer;" [dtOverlayConfig]="config" > Hover me </span> <ng-template #overlay> <p>Overlay content</p> </ng-template> export class OverlayDefaultExample { config: DtOverlayConfig = { pinnable: true, originY: 'center', }; }

Imports

You have to import the DtOverlayModule when you want to use the DtOverlay service or the DtOverlayTriggerdirective. The DtOverlay service also requires Angular's BrowserAnimationsModule for animations. For more details on this see Step 2: Animations in the getting started guide.

@NgModule({
  imports: [DtOverlayModule],
})
class MyModule {}

DtOverlayTrigger directive

The DtOverlayTrigger directive can be applied to any element with the [dtOverlay]="overlay" attribute. The parameter overlay passed in this example is a reference to an ng-template that holds the content of the overlay. The configuration can be passed to the directive using the [dtOverlayConfig] input (see properties below).

The trigger has keyboard support: It opens the overlay on SPACE and ENTER when focused and closes the open overlay on ESCAPE.

When an overlay is active and the trigger gets destroyed, the overlay will be dismissed as well.

Options & properties

Name Type Default Description
@Input() disabled boolean - When disabled is set to true the overlay does not open on hover.
@Input() tabIndex number 0 Sets the trigger's tabindex.
@Input() dtOverlay TemplateRef - Overlay pane containing the content.
@Input() dtOverlayConfig DtOverlayConfig - Overlay configuration; see properties below.
focus() void Focuses the trigger.

DtOverlay service

You can get the DtOverlay service instance with DI in any of your components.

...
constructor(private _dtOverlay: DtOverlay) {}
...

The DtOverlay service has the following two methods:

Method Parameters Return value Description
create() origin: ElementRef
componentOrTemplateRef: ComponentType<T> | TemplateRef<T>
config?: DtOverlayConfig
DtOverlayRef (see details below) Creates a overlay connected to the origin and containing the given component or templateRef.
dismiss() - void Dismisses the currently open overlay. This method can be used if the overlay needs to be dismissed programatically.

DtOverlayRef

The DtOverlay service create method returns an instance of DtOverlayRef. This reference can be used to perform updates on the position, pinning the overlay or dismissing it.

Method Parameters Return value Description
pin() value: boolean void Either pins or unpins the overlay.
dismiss() - void Dismisses the overlay.
updatePosition() offsetX: number
offsetY: number
void Sets an offset to the overlay's position. Can be used e.g. in a mousemove eventhandler to update the overlays position depending on the MouseEvent.

DtOverlayConfig

This configuration is optional and can be passed to the service's create method or as an input to the trigger directive. The DtOverlayConfig class has the following properties:

Name Type Default Description
pinnable boolean - enables pinning of the overlay on click or by keyboard when the trigger is focused.
originY center | edge center The originY defines the vertical attachment point for the overlay. By default center is set. edge defines that the vertical attachment point is set to the bottom edge if the overlay fits below the origin element and the top edge otherwise.
movementConstraint xAxis | yAxis - The movementConstraint locks the movement of the overlay to a given axis. No constraint is set by default.

Examples

Timeline overlay

´ Loading interactive demo...
<div class="timeline" tabindex="-1" #timeline [dtOverlay]="overlay" [dtOverlayConfig]="config" (mouseover)="_onMouseOver()" (mouseout)="_onMouseOut()" > <ng-content></ng-content> </div> <ng-template #overlay> <span>{{ time | date: 'mm:ss' }}</span> </ng-template> export class TimelineComponent { @ViewChild('timeline', { read: ElementRef, static: true }) timeline: ElementRef; config: DtOverlayConfig = { movementConstraint: 'xAxis', originY: 'edge', }; time: Date; duration = 90; private _moveSub = Subscription.EMPTY; constructor(public elementRef: ElementRef, private _ngZone: NgZone) { const date = new Date(); date.setMinutes(0); date.setSeconds(0); this.time = date; } _onMouseOver(): void { this._ngZone.runOutsideAngular(() => { this._moveSub = fromEvent(this.elementRef.nativeElement, 'mousemove') .pipe( map((ev: MouseEvent) => ev.offsetX), distinctUntilChanged(), map(offset => this._calculateTime(offset)), ) .subscribe(offset => { this._ngZone.run(() => { const minutes = Math.floor(offset / 60); const seconds = Math.floor(offset - minutes * 60); const date = new Date(); date.setSeconds(seconds); date.setMinutes(minutes); this.time = date; }); }); }); } _onMouseOut(): void { this._moveSub.unsubscribe(); } private _calculateTime(offset: number): number { const boundingBox = this.elementRef.nativeElement.getBoundingClientRect(); const secPerPixel = this.duration / boundingBox.width; return Math.floor(secPerPixel * offset); } } @Component({ selector: 'dt-timeline-point', template: ` <div class="point" [dtOverlay]="overlay" [dtOverlayConfig]="config" [ngStyle]="{ transform: _translation }" ></div> <ng-template #overlay> <p>Page Load: page/orange.jsf</p> <button dt-button>View highlight</button> </ng-template> `, styles: [ ` .point { border-radius: 50%; width: 20px; height: 20px; display: inline-block; background-color: #c396e0; border: 1px solid #ffffff; text-align: center; line-height: 20px; color: white; font-size: 14px; position: absolute; top: 0px; } p { margin-top: 0; } `, ], }) export class TimelinePointComponent { config: DtOverlayConfig = { pinnable: true, }; @Input() id: number; @Input() get position(): number { return this._position; } set position(value: number) { this._position = value; } private _position = 0; get _translation(): string { if (this._timeline) { const timelineRect = this._timeline.elementRef.nativeElement.getBoundingClientRect() as ClientRect; const translation = Math.max( 0, (timelineRect.width / 100) * this.position - 20, ); return `translate(${translation}px)`; } return 'translate(0)'; } constructor(@Optional() @SkipSelf() private _timeline: TimelineComponent) {} } @Component({ moduleId: module.id, selector: 'demo-component', template: ` <dt-timeline> <dt-timeline-point id="1" position="10"></dt-timeline-point> <dt-timeline-point id="2" position="30"></dt-timeline-point> <dt-timeline-point id="3" position="40"></dt-timeline-point> </dt-timeline> `, }) export class OverlayTimelineExample {} <div class="timeline" tabindex="-1" #timeline [dtOverlay]="overlay" [dtOverlayConfig]="config" (mouseover)="_onMouseOver()" (mouseout)="_onMouseOut()" > <ng-content></ng-content> </div> <ng-template #overlay> <span>{{ time | date: 'mm:ss' }}</span> </ng-template> export class TimelineComponent { @ViewChild('timeline', { read: ElementRef, static: true }) timeline: ElementRef; config: DtOverlayConfig = { movementConstraint: 'xAxis', originY: 'edge', }; time: Date; duration = 90; private _moveSub = Subscription.EMPTY; constructor(public elementRef: ElementRef, private _ngZone: NgZone) { const date = new Date(); date.setMinutes(0); date.setSeconds(0); this.time = date; } _onMouseOver(): void { this._ngZone.runOutsideAngular(() => { this._moveSub = fromEvent(this.elementRef.nativeElement, 'mousemove') .pipe( map((ev: MouseEvent) => ev.offsetX), distinctUntilChanged(), map(offset => this._calculateTime(offset)), ) .subscribe(offset => { this._ngZone.run(() => { const minutes = Math.floor(offset / 60); const seconds = Math.floor(offset - minutes * 60); const date = new Date(); date.setSeconds(seconds); date.setMinutes(minutes); this.time = date; }); }); }); } _onMouseOut(): void { this._moveSub.unsubscribe(); } private _calculateTime(offset: number): number { const boundingBox = this.elementRef.nativeElement.getBoundingClientRect(); const secPerPixel = this.duration / boundingBox.width; return Math.floor(secPerPixel * offset); } } @Component({ selector: 'dt-timeline-point', template: ` <div class="point" [dtOverlay]="overlay" [dtOverlayConfig]="config" [ngStyle]="{ transform: _translation }" ></div> <ng-template #overlay> <p>Page Load: page/orange.jsf</p> <button dt-button>View highlight</button> </ng-template> `, styles: [ ` .point { border-radius: 50%; width: 20px; height: 20px; display: inline-block; background-color: #c396e0; border: 1px solid #ffffff; text-align: center; line-height: 20px; color: white; font-size: 14px; position: absolute; top: 0px; } p { margin-top: 0; } `, ], }) export class TimelinePointComponent { config: DtOverlayConfig = { pinnable: true, }; @Input() id: number; @Input() get position(): number { return this._position; } set position(value: number) { this._position = value; } private _position = 0; get _translation(): string { if (this._timeline) { const timelineRect = this._timeline.elementRef.nativeElement.getBoundingClientRect() as ClientRect; const translation = Math.max( 0, (timelineRect.width / 100) * this.position - 20, ); return `translate(${translation}px)`; } return 'translate(0)'; } constructor(@Optional() @SkipSelf() private _timeline: TimelineComponent) {} } @Component({ moduleId: module.id, selector: 'demo-component', template: ` <dt-timeline> <dt-timeline-point id="1" position="10"></dt-timeline-point> <dt-timeline-point id="2" position="30"></dt-timeline-point> <dt-timeline-point id="3" position="40"></dt-timeline-point> </dt-timeline> `, }) export class OverlayTimelineExample {}

Programmatic overlay

´ Loading interactive demo...
overlay export class DummyOverlay {} @Component({ moduleId: module.id, selector: 'demo-component', template: ` <button dt-button (click)="createOverlay()">Create overlay</button> <button dt-button (click)="dismiss()">Dismiss</button> <p><span #origin>An overlay will be created here</span></p> `, }) export class OverlayProgrammaticExample { @ViewChild('origin', { static: true }) origin: ElementRef; constructor(private _dtOverlay: DtOverlay) {} createOverlay(): void { this._dtOverlay.create(this.origin, DummyOverlay); } dismiss(): void { this._dtOverlay.dismiss(); } } overlay export class DummyOverlay {} @Component({ moduleId: module.id, selector: 'demo-component', template: ` <button dt-button (click)="createOverlay()">Create overlay</button> <button dt-button (click)="dismiss()">Dismiss</button> <p><span #origin>An overlay will be created here</span></p> `, }) export class OverlayProgrammaticExample { @ViewChild('origin', { static: true }) origin: ElementRef; constructor(private _dtOverlay: DtOverlay) {} createOverlay(): void { this._dtOverlay.create(this.origin, DummyOverlay); } dismiss(): void { this._dtOverlay.dismiss(); } }