diff --git a/packages/components/src/higher-order/with-focus-outside/index.native.js b/packages/components/src/higher-order/with-focus-outside/index.native.js
new file mode 100644
index 0000000000000..7d3ae52eaeb78
--- /dev/null
+++ b/packages/components/src/higher-order/with-focus-outside/index.native.js
@@ -0,0 +1,139 @@
+/**
+ * External dependencies
+ */
+import { includes } from 'lodash';
+import { View } from 'react-native';
+
+/**
+ * WordPress dependencies
+ */
+import { Component } from '@wordpress/element';
+import { createHigherOrderComponent } from '@wordpress/compose';
+
+/**
+ * Input types which are classified as button types, for use in considering
+ * whether element is a (focus-normalized) button.
+ *
+ * @type {string[]}
+ */
+const INPUT_BUTTON_TYPES = [
+ 'button',
+ 'submit',
+];
+
+/**
+ * Returns true if the given element is a button element subject to focus
+ * normalization, or false otherwise.
+ *
+ * @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#Clicking_and_focus
+ *
+ * @param {Element} element Element to test.
+ *
+ * @return {boolean} Whether element is a button.
+ */
+function isFocusNormalizedButton( element ) {
+ switch ( element.nodeName ) {
+ case 'A':
+ case 'BUTTON':
+ return true;
+
+ case 'INPUT':
+ return includes( INPUT_BUTTON_TYPES, element.type );
+ }
+
+ return false;
+}
+
+export default createHigherOrderComponent(
+ ( WrappedComponent ) => {
+ return class extends Component {
+ constructor() {
+ super( ...arguments );
+
+ this.bindNode = this.bindNode.bind( this );
+ this.cancelBlurCheck = this.cancelBlurCheck.bind( this );
+ this.queueBlurCheck = this.queueBlurCheck.bind( this );
+ this.normalizeButtonFocus = this.normalizeButtonFocus.bind( this );
+ }
+
+ componentWillUnmount() {
+ this.cancelBlurCheck();
+ }
+
+ bindNode( node ) {
+ if ( node ) {
+ this.node = node;
+ } else {
+ delete this.node;
+ this.cancelBlurCheck();
+ }
+ }
+
+ queueBlurCheck( event ) {
+ // React does not allow using an event reference asynchronously
+ // due to recycling behavior, except when explicitly persisted.
+ event.persist();
+
+ // Skip blur check if clicking button. See `normalizeButtonFocus`.
+ if ( this.preventBlurCheck ) {
+ return;
+ }
+
+ this.blurCheckTimeout = setTimeout( () => {
+ if ( 'function' === typeof this.node.handleFocusOutside ) {
+ this.node.handleFocusOutside( event );
+ }
+ }, 0 );
+ }
+
+ cancelBlurCheck() {
+ clearTimeout( this.blurCheckTimeout );
+ }
+
+ /**
+ * Handles a mousedown or mouseup event to respectively assign and
+ * unassign a flag for preventing blur check on button elements. Some
+ * browsers, namely Firefox and Safari, do not emit a focus event on
+ * button elements when clicked, while others do. The logic here
+ * intends to normalize this as treating click on buttons as focus.
+ *
+ * @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#Clicking_and_focus
+ *
+ * @param {MouseEvent} event Event for mousedown or mouseup.
+ */
+ normalizeButtonFocus( event ) {
+ const { type, target } = event;
+
+ const isInteractionEnd = includes( [ 'mouseup', 'touchend' ], type );
+
+ if ( isInteractionEnd ) {
+ this.preventBlurCheck = false;
+ } else if ( isFocusNormalizedButton( target ) ) {
+ this.preventBlurCheck = true;
+ }
+ }
+
+ render() {
+ // Disable reason: See `normalizeButtonFocus` for browser-specific
+ // focus event normalization.
+
+ /* eslint-disable jsx-a11y/no-static-element-interactions */
+ return (
+
+
+
+ );
+ /* eslint-enable jsx-a11y/no-static-element-interactions */
+ }
+ };
+ }, 'withFocusOutside'
+);
diff --git a/packages/components/src/index.native.js b/packages/components/src/index.native.js
index 3e44bdec61e50..cbaae28e089bd 100644
--- a/packages/components/src/index.native.js
+++ b/packages/components/src/index.native.js
@@ -9,3 +9,4 @@ export { createSlotFill, Slot, Fill, Provider as SlotFillProvider } from './slot
// Higher-Order Components
export { default as withFilters } from './higher-order/with-filters';
+export { default as withFocusOutside } from './higher-order/with-focus-outside';
diff --git a/packages/editor/src/components/index.native.js b/packages/editor/src/components/index.native.js
index 229efa5879cc3..d226b4fe46480 100644
--- a/packages/editor/src/components/index.native.js
+++ b/packages/editor/src/components/index.native.js
@@ -7,5 +7,6 @@ export { default as BlockFormatControls } from './block-format-controls';
export { default as BlockControls } from './block-controls';
export { default as BlockEdit } from './block-edit';
export { default as DefaultBlockAppender } from './default-block-appender';
+export { default as PostTitle } from './post-title';
export { default as EditorHistoryRedo } from './editor-history/redo';
export { default as EditorHistoryUndo } from './editor-history/undo';
diff --git a/packages/editor/src/components/post-title/index.native.js b/packages/editor/src/components/post-title/index.native.js
new file mode 100644
index 0000000000000..61148e0f4e7a6
--- /dev/null
+++ b/packages/editor/src/components/post-title/index.native.js
@@ -0,0 +1,84 @@
+/**
+ * External dependencies
+ */
+import { TextInput } from 'react-native';
+
+/**
+ * WordPress dependencies
+ */
+import { Component } from '@wordpress/element';
+import { decodeEntities } from '@wordpress/html-entities';
+import { withDispatch } from '@wordpress/data';
+import { withFocusOutside } from '@wordpress/components';
+import { withInstanceId, compose } from '@wordpress/compose';
+
+class PostTitle extends Component {
+ constructor() {
+ super( ...arguments );
+
+ this.onChange = this.onChange.bind( this );
+ this.onSelect = this.onSelect.bind( this );
+ this.onUnselect = this.onUnselect.bind( this );
+
+ this.state = {
+ isSelected: false,
+ };
+ }
+
+ handleFocusOutside() {
+ this.onUnselect();
+ }
+
+ onSelect() {
+ this.setState( { isSelected: true } );
+ this.props.clearSelectedBlock();
+ }
+
+ onUnselect() {
+ this.setState( { isSelected: false } );
+ }
+
+ onChange( title ) {
+ this.props.onUpdate( title );
+ }
+
+ render() {
+ const {
+ placeholder,
+ style,
+ title,
+ } = this.props;
+
+ const decodedPlaceholder = decodeEntities( placeholder );
+
+ return (
+
+
+ );
+ }
+}
+
+const applyWithDispatch = withDispatch( ( dispatch ) => {
+ const {
+ clearSelectedBlock,
+ } = dispatch( 'core/editor' );
+
+ return {
+ clearSelectedBlock,
+ };
+} );
+
+export default compose(
+ applyWithDispatch,
+ withInstanceId,
+ withFocusOutside
+)( PostTitle );