diff --git a/sandbox/transient-button.html.example b/sandbox/transient-button.html.example
new file mode 100644
index 0000000000..ab9c10e26d
--- /dev/null
+++ b/sandbox/transient-button.html.example
@@ -0,0 +1,102 @@
+
+
+
+
+ Video.js Sandbox
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/css/components/_transient-button.scss b/src/css/components/_transient-button.scss
new file mode 100644
index 0000000000..6adb924c9a
--- /dev/null
+++ b/src/css/components/_transient-button.scss
@@ -0,0 +1,58 @@
+.video-js .vjs-transient-button {
+ position: absolute;
+ height: 3em;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background-color: rgba(50, 50, 50, 0.5);
+ cursor: pointer;
+ opacity: 1;
+ transition: opacity 1s;
+}
+
+.video-js .vjs-transient-button span {
+ padding: 0 0.5em;
+}
+
+.video-js .vjs-transient-button.left {
+ left: 5px;
+}
+
+.video-js .vjs-transient-button.right {
+ right: 5px;
+}
+
+.video-js .vjs-transient-button.top {
+ top: 5px;
+}
+
+.video-js .vjs-transient-button.bottom {
+ bottom: 35px;
+}
+
+.video-js.vjs-layout-large .vjs-transient-button {
+ height: 5em;
+ padding: 0 1em;
+ font-size: 120%;
+}
+
+.video-js.vjs-layout-x-large .vjs-transient-button,
+.video-js.vjs-layout-huge .vjs-transient-button {
+ height: 8em;
+ padding: 0 1.5em;
+
+ font-size: 150%;
+}
+
+.video-js .vjs-transient-button.top.avoid-title {
+ top: 80px;
+}
+
+.video-js .vjs-transient-button:hover {
+ background-color: rgba(50, 50, 50, 0.9);
+}
+
+.video-js.not-hover .vjs-transient-button:not(.force-display),
+.video-js.vjs-user-inactive .vjs-transient-button:not(.force-display) {
+ opacity: 0;
+}
diff --git a/src/css/video-js.scss b/src/css/video-js.scss
index cf20e45241..fcb8388f20 100644
--- a/src/css/video-js.scss
+++ b/src/css/video-js.scss
@@ -44,6 +44,7 @@
@import "components/captions-settings";
@import "components/title-bar";
@import "components/skip-buttons";
+@import "components/transient-button";
@import "print";
diff --git a/src/js/transient-button.js b/src/js/transient-button.js
new file mode 100644
index 0000000000..07f72c3b53
--- /dev/null
+++ b/src/js/transient-button.js
@@ -0,0 +1,77 @@
+import Button from './button.js';
+import Component from './component.js';
+import {merge} from './utils/obj';
+import * as Dom from './utils/dom.js';
+
+// Options type will need to extend button options, because `className`, `clickHandler` may be used
+// `position` is redeundant with `className` but allows position to be set without overriding className or vice versa.
+const defaults = {
+ forceTimeout: 4000,
+ position: 'bottom left'
+};
+
+/**
+ * A floating transient button
+ *
+ * @extends Button
+ */
+class TransientButton extends Button {
+ /**
+ *
+ * @param { import('./player').default } player
+ *
+ * @param {object} options
+ */
+ constructor(player, options) {
+ options = merge(defaults, options);
+ super(player, options);
+ this.controlText(options.controlText);
+ this.hide();
+
+ // When shown, the float button will be visible even if the user is inactive.
+ // Clear this if there is any interaction.
+ player.on(['useractive', 'userinactive'], () => {
+ this.removeClass('force-display');
+ });
+ }
+
+ buildCSSClass() {
+ return `vjs-transient-button ${this.options_.position}`;
+ }
+
+ createEl() {
+ /** @type HTMLButtonElement */
+ const el = Dom.createEl(
+ 'button', {}, {
+ type: 'button',
+ class: this.buildCSSClass()
+ },
+ Dom.createEl('span')
+ );
+
+ this.controlTextEl_ = el.querySelector('span');
+
+ return el;
+ }
+
+ show() {
+ super.show();
+ this.addClass('force-display');
+ this.forceDisplayTimeout = this.player_.setTimeout(() => {
+ this.removeClass('force-display');
+ }, this.options_.forceTimeout);
+ }
+
+ hide() {
+ this.removeClass('force-display');
+ super.hide();
+ }
+
+ dispose() {
+ this.player_.clearTimeout(this.forceDisplayTimeout);
+ super.dispose();
+ }
+}
+
+Component.registerComponent('TransientButton', TransientButton);
+export default TransientButton;