diff --git a/components/video/index.js b/components/video/index.js
new file mode 100644
index 00000000..caa5bfcd
--- /dev/null
+++ b/components/video/index.js
@@ -0,0 +1,2 @@
+export { Video } from './video';
+export { VideoControl } from './video-control';
diff --git a/components/video/readme.md b/components/video/readme.md
new file mode 100644
index 00000000..8540b4d1
--- /dev/null
+++ b/components/video/readme.md
@@ -0,0 +1,122 @@
+# Video
+
+The Video component allows you to easily add videos to your custom blocks without needing to manually worry about loading states etc. It renders a `` component in place of the video if the id is not set and shows a spinner when the video is still loading.
+
+## Usage
+
+```js
+import { Video } from '@10up/block-components';
+
+function BlockEdit(props) {
+ const { attributes, setAttributes } = props;
+ const { videoId } = attributes;
+
+ function handlevideoSelect( video ) {
+ setAttributes({videoId: video.id});
+ }
+
+ return (
+
+ )
+}
+```
+
+If you'd like to make an Inspector control for this video, use `VideoControl` instead.
+
+```js
+import { Video } from '@10up/block-components';
+
+function BlockEdit(props) {
+ const { attributes, setAttributes } = props;
+ const { videoId } = attributes;
+
+ function handlevideoSelect( video ) {
+ setAttributes({videoId: video.id});
+ }
+
+ return (
+
+ )
+}
+```
+
+While you can set the same `autoPlay`, `loop`, and `muted` properties to the `Video` or `VideoControl` directly, you can also allow the user to control these settings by passing a `onChangeVideoOptions` callback, and a `videoOptions` setting object:
+
+```js
+import { Video } from '@10up/block-components';
+
+function BlockEdit(props) {
+ const { attributes, setAttributes } = props;
+ const {
+ videoId,
+ videoOptions = {
+ playsInline: true,
+ controls: true,
+ muted: true,
+ autoPlay: false,
+ loop: false,
+ }
+ } = attributes;
+
+ function handleVideoSelect( video ) {
+ setAttributes({videoId: video.id});
+ }
+
+ function handleChangeVideoOptions( options ) {
+ setAttributes({videoOptions: options});
+ }
+
+ return (
+
+ )
+}
+```
+
+
+
+##
+
+> **Note**
+> In order to get the same result as the GIF you also need to use the [`MediaToolbar`](https://github.com/10up/block-components/tree/develop/components/media-toolbar) component. It adds the Replace flow to the Blocks Toolbar.
+
+## Props
+
+| Name | Type | Default | Description |
+| ---------- | ----------------- | -------- | -------------------------------------------------------------- |
+| `id` | `number` | `null` | video ID |
+| `onSelect` | `Function` | `null` | Callback that gets called with the new video when one is selected |
+| `size` | `string` | `large` | Name of the video size to be displayed |
+| `labels` | `object` | `{}` | Pass in an object of labels to be used by the `MediaPlaceholder` component under the hook. Allows the sub properties `title` and `instructions` |
+| `canEditVideo` | `boolean` | `true` | whether or not the video can be edited by in the context its getting viewed. Controls whether a placeholder or upload controls should be shown when no video is present |
+| `...rest` | `*` | `null` | any additional attributes you want to pass to the underlying `img` tag |
diff --git a/components/video/video-control.js b/components/video/video-control.js
new file mode 100644
index 00000000..7c3afcdb
--- /dev/null
+++ b/components/video/video-control.js
@@ -0,0 +1,170 @@
+/**
+ * wraps the Video component with controls to allow use for uploads in the sidebar
+ */
+
+import PropTypes from 'prop-types';
+import { MediaUploadCheck, MediaUpload } from '@wordpress/block-editor';
+import {
+ Placeholder,
+ BaseControl,
+ useBaseControlProps,
+ Button,
+ ToggleControl,
+} from '@wordpress/components';
+import { __ } from '@wordpress/i18n';
+import { Video } from './video';
+
+const VideoControl = (props) => {
+ const {
+ id,
+ allowedTypes = ['video/mp4'],
+ onSelect,
+ labels: { title = __('Video upload'), instructions = __('Choose a video') },
+ canEditVideo = true,
+ hideVideo = false,
+ video,
+ videoOptions = {
+ playsInline: true,
+ controls: true,
+ muted: true,
+ autoPlay: false,
+ loop: false,
+ },
+ onChangeVideoOptions,
+ } = props;
+ const hasVideo = !!id;
+
+ const { playsInline, controls, muted, autoPlay, loop } = videoOptions;
+
+ const { baseControlProps, controlProps } = useBaseControlProps(props);
+
+ if (!hasVideo && !canEditVideo) {
+ return ;
+ }
+
+ if (!hasVideo && canEditVideo) {
+ return (
+
+
+ (
+
+ )}
+ {...controlProps}
+ />
+
+
+ );
+ }
+
+ return (
+
+ {!hideVideo && (
+
+ )}
+
+ (
+
+ )}
+ />
+
+ {onChangeVideoOptions && (
+ <>
+ {
+ console.log(value);
+ onChangeVideoOptions({ autoPlay: value, loop, muted });
+ }}
+ />
+ {
+ console.log(value);
+ onChangeVideoOptions({ autoPlay, muted: value, loop });
+ }}
+ />
+ {
+ console.log(value);
+ onChangeVideoOptions({ autoPlay, muted, loop: value });
+ }}
+ />
+ >
+ )}
+
+ );
+};
+
+VideoControl.defaultProps = {
+ labels: {
+ title: __('Video upload'),
+ instructions: __('Choose a video'),
+ },
+ canEditVideo: true,
+ hideVideo: false,
+ allowedTypes: ['video/mp4'],
+ videoOptions: {
+ playsInline: true,
+ controls: true,
+ muted: true,
+ autoPlay: false,
+ loop: false,
+ },
+ onChangeVideoOptions: null,
+};
+
+VideoControl.propTypes = {
+ id: PropTypes.number.isRequired,
+ onSelect: PropTypes.func.isRequired,
+ labels: PropTypes.shape({
+ title: PropTypes.string,
+ instructions: PropTypes.string,
+ }),
+ canEditVideo: PropTypes.bool,
+ hideVideo: PropTypes.bool,
+ allowedTypes: PropTypes.array,
+ videoOptions: PropTypes.shape({
+ playsInline: PropTypes.bool,
+ controls: PropTypes.bool,
+ muted: PropTypes.bool,
+ autoPlay: PropTypes.bool,
+ loop: PropTypes.bool,
+ }),
+ onChangeVideoOptions: PropTypes.func,
+};
+
+export { VideoControl };
diff --git a/components/video/video.js b/components/video/video.js
new file mode 100644
index 00000000..1bf8a516
--- /dev/null
+++ b/components/video/video.js
@@ -0,0 +1,71 @@
+import { MediaPlaceholder } from '@wordpress/block-editor';
+import { Spinner, Placeholder } from '@wordpress/components';
+
+import { useMedia, PropTypes } from '@10up/block-components';
+
+const Video = (props) => {
+ const {
+ id,
+ size = 'full',
+ onSelect,
+ labels = {},
+ canEditVideo = true,
+ className = '',
+ playsInline = true,
+ autoPlay = false,
+ loop = false,
+ muted = true,
+ controls = true,
+ } = props;
+ const hasVideo = !!id;
+ const { media, isResolvingMedia } = useMedia(id);
+
+ if (!hasVideo && !canEditVideo) {
+ return ;
+ }
+
+ if (!hasVideo && canEditVideo) {
+ return (
+
+ );
+ }
+
+ if (isResolvingMedia) {
+ return ;
+ }
+
+ const sourceUrl = media?.media_details?.sizes[size]?.source_url ?? media?.source_url;
+ const mimeType = media?.media_details?.type ?? '';
+ const altText = media?.alt_text;
+
+ return (
+
+ );
+};
+
+Video.defaultProps = {
+ labels: {},
+ canEditVideo: true,
+};
+
+Video.propTypes = {
+ id: PropTypes.number.isRequired,
+ onSelect: PropTypes.func.isRequired,
+ labels: PropTypes.shape({
+ title: PropTypes.string,
+ instructions: PropTypes.string,
+ }),
+ canEditVideo: PropTypes.bool,
+};
+
+export { Video };