discussions-and-proposals
discussions-and-proposals copied to clipboard
RFC: React DOM for Native (reduce API fragmentation)
Updated: September 2023
The contents of the RFC are reproduced below.
Also see RFC: DOM traversal and layout APIs in React Native
RFC: React DOM for Native
Summary
This is a proposal to incrementally reduce the API fragmentation faced by developers using React to target multiple platforms via code shared between native and web. The proposed cross-platform user interface APIs are a subset of existing web standards for DOM, CSS, and HTML - "Strict DOM". The proposed changes are overwhelmingly additive and do not require migration of existing React Native UI code (deprecations are optional / follow up work). Incremental progress can help to reduce the fragmentation between React Native and React DOM components, while the ability to run React DOM code (with minor modifications) on native is a longer-term goal.
Motivation
React Native currently includes many APIs that are modelled on Web APIs but do not conform to the standards of those Web APIs. React Native also includes many APIs that achieve the same results on Android and iOS but are exposed as 2 different props. And React Native includes several APIs that have known performance (network and runtime) drawbacks on Web.
This proposal aims to allow developers to target more platforms with cross-platform APIs, and deliver better performance when targeting browsers. Features for Android, iOS, and Web are unified by aligning with the Web standard. Supporting standards helps to:
- minimize the overhead when running in browsers;
- reduce developer education required to learn features;
- set clear and cohesive API end-state expectations for contributors to aim for;
- accelerate the framework's development by avoiding API design costs;
- support backwards compatibility;
- develop universal codebases.
The existing solution for targeting web with React Native is to use React Native for Web. React Native for Web is a user-space library built on top of React DOM and native DOM APIs. It shims React Native components and APIs on the web. The tooling for an existing React DOM app simply maps the 'react-native' export to 'react-native-web'.
This is the most complete and widely used shim, but comes with considerable DX and UX costs on the web. The shim must implement a large surface area of fragmented APIs, and it needs to modify standard APIs and objects (e.g., events) to match React Native's non-standard implementations.
In contrast, implementing a "Strict DOM" subset in React Native shifts the weight of bridging native and web apps onto React Native, where it can be done most efficiently. Although React Native will not support all the features available on web, it will still support a greater expanded feature set relative to React Native today. On the web, we would only need to combine React DOM with a white-label CSS compiler like stylex.
Detailed design
The "complete" API involved is broken down into the following sections.
- Environment API. Global APIs to add to the host environment.
- Elements API. DOM APIs to add to host component instances.
- Components API. React DOM components to add to React Native.
- Props API. React DOM component props to add to new components, and existing React Native components as part of an incremental strategy.
- Styling API. CSS APIs to add to the React Native environment.
A simplified example of the user-space code we'd aim to support is as follows:
import { html, css } from 'react-native/dom';
export function VStackPanel(props) {
const connectedCallback = function (node) {
const handler = () => {
const { offsetWidth } = e.target;
// ...
};
const options = { capture: true };
node.addEventListener('pointerdown', handler, options);
return function disconnected() {
node.removeEventListener('pointerdown', handler, options)
}
};
return (
// Use HTML element
<html.div
children={props.children}
// Synchronous click event that matches W3C event type
on-click={props.click}
// Use HTMLElement instance API
ref={connectedCallback}
// Use optimized styles
style={css(
{ flexDirection: 'column', flex: 1 },
props.style
)}
/>
)
}
But there are many incremental steps that can be taken along the way.
Implementation and adoption strategy
The proposal is to support both an incremental adoption strategy, as well as a separate "React DOM" bindings exports. The incremental strategy would find ways to align existing APIs in React Native with their React DOM equivalents, and gradually deprecate the non-standard APIs. Whereas the separate bindings export would be designed to provide most of what is needed for a direct compatibility layer with React DOM.
This would ensure a significant amount of overlap between the existing React Native components and the dedicated "React DOM" compatibility API, allowing for co-mingling of code and reduced developer education. There is no suggestion of deprecating the current React Native components and APIs at this stage.
Reduce API fragmentation across platforms
The initial step is to incrementally reduce existing API fragmenetation across platforms. This involves consolidating the different props, styles, and events for similar functionality across different platforms. The APIs that we consolidate upon are the W3C standards, as React Native is already fairly closely aligned with these standards.
Doing this will make it easier for web developers to work with React Native's existing components and related APIs. This is the path for developers to gradually migrate their existing React Native code into a form that is more aligned with the web. And it will allow React Native for Web to be smaller and faster, providing better results on web via the existing tools that developers use today.
One example of this kind of incremental change is to map props in the existing core components (i.e., View
, Text
, etc.) to the W3C equivalents: id
to nativeID
, aria-label
to accessibilityLabel
, and so on. React Native for Web currently "owns" this translation step, but we would instead move it into React Native to claw back performance on web. For developers familiar with React DOM, there will be less of a learning curve to applying that knowledge to React Native.
<View
aria-hidden={false}
aria-label="Accessibility label"
id="native-id"
on-click={handleDOMClick}
on-pointerdown={handlePointerDown}
role="button"
tabIndex={0}
>
<Image
alt="Alternative text"
crossOrigin="anonymous"
srcSet="https://image.png 1x, https://image2.png 2x"
width={320}
>
<TextArea
autoComplete="email"
inputMode="email"
/>
<TextArea
multiline
readOnly
rows={3}
/>
Example of how to map srcSet
to source
:
const { crossOrigin, referrerPolicy, srcSet } = props;
const source = [];
const srcList = srcSet.split(', ');
srcList.forEach((src) => {
const [uri, xscale] = src.split(' ');
const scale = parseInt(xscale.split('x')[0], 10);
const headers = {};
if (crossOrigin === 'use-credentials') {
headers['Access-Control-Allow-Credentials'] = true;
}
if (referrerPolicy != null) {
headers['Referrer-Policy'] = referrerPolicy;
}
source.push({ headers, height, scale, uri, width });
});
Example of how to map autoComplete
to Android's autoComplete
and iOS's textContentType
:
// https://reactnative.dev/docs/textinput#autocomplete-android
const autoCompleteAndroid = {
'address-line1': 'postal-address-region',
'address-line2': 'postal-address-locality',
bday: 'birthdate-full',
'bday-day': 'birthdate-day',
'bday-month': 'birthdate-month',
'bday-year': 'birthdate-year',
'cc-csc': 'cc-csc',
'cc-exp': 'cc-exp',
'cc-exp-month': 'cc-exp-month',
'cc-exp-year': 'cc-exp-year',
'cc-number': 'cc-number',
country: 'postal-address-country',
'current-password': 'password',
email: 'email',
name: 'name',
'additional-name': 'name-middle',
'family-name': 'name-family',
'given-name': 'name-given',
'honorific-prefix': 'namePrefix',
'honorific-suffix': 'nameSuffix',
'new-password': 'password-new',
off: 'off',
'one-time-code': 'sms-otp',
'postal-code': 'postal-code',
sex: 'gender',
'street-address': 'street-address',
tel: 'tel',
'tel-country-code': 'tel-country-code',
'tel-national': 'tel-national',
username: 'username'
};
// https://reactnative.dev/docs/textinput#textcontenttype-ios
const autoCompleteIOS = {
'address-line1': 'streetAddressLine1',
'address-line2': 'streetAddressLine2',
'cc-number': 'creditCardNumber',
'current-password': 'password',
country: 'countryName',
email: 'emailAddress',
name: 'name',
'additional-name': 'middleName',
'family-name': 'familyName',
'given-name': 'givenName',
nickname: 'nickname',
'honorific-prefix': 'name-prefix',
'honorific-suffix': 'name-suffix',
'new-password': 'newPassword',
off: 'none',
'one-time-code': 'oneTimeCode',
organization: 'organizationName',
'organization-title': 'jobTitle',
'postal-code': 'postalCode',
'street-address': 'fullStreetAddress',
tel: 'telephoneNumber',
url: 'URL',
username: 'username'
};
Where possible we should make these changes to the native code. However, we will prioritize user-space shims in JavaScript where they are faster to ship and allow us to gather feedback/support for this overall direction.
Beyond props, there is also the opportunity to reduce fragementation in styling APIs.
- [ ] Don't hard crash on unknown or invalid style properties and values.
- [ ] Deprecate
StyleSheet.absoluteFill
. Useposition:'absolute';inset:0;
instead. - [ ] Deprecate
StyleSheet.absoluteFillObject
. - [ ] Deprecate
StyleSheet.compose()
. Use existing[ a, b ]
syntax instead. - [ ] Deprecate
StyleSheet.flatten()
. This API encourages runtime introspection of styles in render calls. This pattern is a performance cost (flattening arrays of objects), and prevents us from supporting build-time optimizations for web (e.g., extracting styles to CSS files). - [ ] Deprecate or rename
setStyleAttributePreprocessor
(e.g.,unstable_setStyleAttributePreprocessor
). - [ ]
StyleSheet.create()
should obfuscate styles to prevent introspection (i.e., revert identify function change). On web, we need to be able to remove the JavaScript style objects from bundles to support build-time optimization like extraction to CSS files.
To encourage React Native library and product developers to proactively migrate to these new APIs, React Native for Web plans to only support these W3C standards-based APIs in future versions. This will allow us to incrementally add APIs to React Native without needing to commit to simultaneously deprecating APIs and migrating existing React Native code. Existing React Native developers must adopt these APIs if they wish to support web.
Create a separate package or export for DOM bindings
The next step would be to begin exporting separate React DOM bindings from React Native. This would aim to allow code originally written with React DOM to run on React Native with only minor modifications. There would be no need to use React Native for Web, and the web layer in this scenario would be a lot smaller, limited mostly to implementing the modern W3C events API and integrating a style API that allows for static extraction to optimized CSS.
Even this can be done in incremental fashion, with early (user-space) prototypes to get feedback. Most tags can be mapped to existing React Native components and props, in the reverse of the mapping used by React Native for Web.
Equivalent API for Web
In parallel, a user-space layer over React DOM would ensure equivalent APIs between native and web, by converging them both towards a desired the "end state" where the differences between React Native and React DOM are erased while landing on a "modern" flavor of React DOM.
The benefit of initially doing this in user-space is that it reduces the burden of complex migratations for React Native and React DOM (e.g., the changes to React DOM event types, the style
prop, and explicit imports of component types), while allowing us to validate and iterate on the proposed end-state APIs with interested parties. This is currently blocked on OSS-ing of stylex.
How we teach this
Teaching these APIs is simplified by developer familiarity with existing DOM / React DOM APIs.
Drawbacks
Many of these features can be shimmed in user-space, at the cost of runtime overhead for native or web platforms. The runtime overhead for web is untenable. The runtime overhead of shifting shims into the React Native JS has yet to be established.
This adds redundancy to the React Native API, as the proposal is to make additive changes and avoid requiring removal of existing APIs in the short term.
We have to be very intentional about how much compatibility with open web standards we can promise. Some cross-platform limitations are rooted in significant differences in the host platforms APIs (e.g., accessibility) and the lack of 99% compat may frustrate users.
Aiming for a degree of React DOM compatibility shifts the burden of missing features onto React Native. Developers may not be used to an API that is complete on web but partially implemented on native platforms.
Environment APIs
window
Events
- [ ] Receives all W3C events dispatched to components (capture and bubble phase). Resolves proposal #249.
- [ ] Dispatches
resize
.
Properties
- [ ]
window.devicePixelRatio
- [ ]
window.navigator
- [ ]
clipboard
- [ ]
languages
- [ ]
permissions
- [ ]
vibrate()
- [ ]
- [ ]
window.performance
Methods
- [ ]
window.addEventListener()
- [ ]
window.dispatchEvent()
- [ ]
window.getSelection()
- [ ]
window.removeEventListener()
APIs
- [ ]
window.fetch
- [ ]
window.matchMedia
. Resolves proposal #350. - [ ]
IntersectionObserver
API for observing the intersection of target elements. This can also be used as a building block for performance tooling. - [ ]
MutationObserver
API for watching changes to the host node tree. This can also be used as a building block for performance tooling. - [ ]
ResizeObserver
API for responding the changes in the size of elements. Note that this API no longer includes positional coordinates and is optimized for width/height information.
document
Support the document
object for common patterns such as listening to visibility changes, and listening to capture or bubble phase events for the entire application (e.g., "outside click" pattern), etc.
The document
object might not be exposed as a global, and would instead be accessed via node.getRootNode()
to ensure code accounts for multi-window / multi-root scenarios that are more common for React Native apps than React DOM apps (rendering into sourceless iframes is a similar use case).
Events
- [ ]
scroll
. - [ ]
visibilitychange
. Can replace theAppState
API.
Properties
- [ ]
document.activeElement
. - [ ]
document.defaultView
. Returns thewindow
object associated with adocument
, ornull
if none is available. - [ ]
document.visibilityState
Methods
- [ ]
document.getElementFromPoint(x,y)
. Resolves proposal #501.
Elements API (DOM subset)
Subset of cross-platform DOM APIs exposed on element instances. This would be an imperative API for working with the Shadow tree in React Native. Based on data from MDN Web API, please refer to the MDN links for complete DOM APIs and comment if you believe any should be included in the proposed subset.
In practice, this DOM API subset is generally limited to the “best practice” APIs that can safely be used with React DOM (i.e., read-only operations that don’t modify the DOM tree), and most React components should already be limited to this subset. One of the important details of this API are how it supports the different ways of determining the dimensions of elements.
EventTarget
The EventTarget
API is implemented in JS environments, and should be available on React Native host elements. This feature is commonly used on web, and could be used by React Native developers to support more complex features without first requiring further core API changes to support each use case.
Methods
- [ ]
EventTarget.addEventListener()
. Registers an event handler to a specific event type on the element. - [ ]
EventTarget.dispatchEvent()
. Dispatches an event to this node in the DOM and returns a boolean value that indicates whether no handler canceled the event. - [ ]
EventTarget.removeEventListener()
. Removes an event listener from the element.
Event and CustomEvent
Event() and CustomEvent() represent events initialized by developers and that can be dispatched to elements using EventTarget.dispatchEvent()
.
Node
The DOM Node interface is an abstract base class upon which many other DOM API objects are based, thus letting those object types to be used similarly and often interchangeably. All objects that implement Node functionality are based on one of its subclasses. Every kind of host node is represented by an interface based on Node.
In some cases, a particular feature of the base Node interface may not apply to one of its child interfaces; in that case, the inheriting node may return null or throw an exception, depending on circumstances.
Static properties
- [x]
Node.ELEMENT_NODE
(1) - [x]
Node.TEXT_NODE
(3) - [x]
Node.DOCUMENT_NODE
(9)
- [x]
Node.DOCUMENT_POSITION_DISCONNECTED
(1) - [x]
Node.DOCUMENT_POSITION_PRECEDING
(2) - [x]
Node.DOCUMENT_POSITION_FOLLOWING
(4) - [x]
Node.DOCUMENT_POSITION_CONTAINS
(8) - [x]
Node.DOCUMENT_POSITION_CONTAINED_BY
(16)
Instance properties
- [x]
node.childNodes
. Not a live collection. - [x]
node.firstChild
- [x]
node.isConnected
- [x]
node.lastChild
- [x]
node.nextSibling
- [x]
node.nodeName
- [x]
node.nodeType
- [x]
node.nodeValue
(always null) - [x]
node.parentElement
- [x]
node.parentNode
- [x]
node.previousSibling
- [x]
node.textContent
Instances methods
Node inherits methods from its parent, EventTarget.
- [x]
node.compareDocumentPosition()
- [x]
node.contains()
. Returns true or false value indicating whether or not a node is a descendant of the calling node. - [ ]
node.getRootNode()
. Returns the context object's root which optionally includes the shadow root if it is available.. - [x]
node.hasChildNodes()
.
Element
Element is the most general base class from which all element objects (i.e. objects that represent elements) in a Document inherit. It only has methods and properties common to all kinds of elements. More specific classes inherit from Element.
Instance properties
Element inherits properties from its parent interface, Node, and by extension that interface's parent, EventTarget.
- [x]
element.childElementCount
. - [x]
element.children
. Not a live collection. - [x]
element.clientHeight
[Read only]. Returns a number representing the inner height of the element. - [x]
element.clientLeft
[Read only]. Returns a number representing the width of the left border of the element. - [x]
element.clientTop
[Read only]. Returns a number representing the width of the top border of the element. - [x]
element.clientWidth
[Read only]. Returns a number representing the inner width of the element. - [x]
element.firstElementChild
. - [x]
element.id
[Read only]. Is a DOMString representing the id of the element. - [x]
element.lastElementChild
. - [x]
element.nextElementSibling
. - [x]
element.previousElementSibling
. - [x]
element.scrollHeight
[Read only]. Returns a number representing the scroll view height of an element. - [x]
element.scrollLeft
. Is a number representing the left scroll offset of the element. - [x]
element.scrollTop
. A number representing number of pixels the top of the element is scrolled vertically. - [x]
element.scrollWidth
[Read only]. Returns a number representing the scroll view width of the element. - [x]
element.tagName
(alias for nodeName).
Instance methods
Element inherits methods from its parents Node, and its own parent, EventTarget.
- [ ]
element.computedStyleMap()
. Returns a StylePropertyMapReadOnly interface which provides a read-only representation of a CSS declaration block that is an alternative to CSSStyleDeclaration. - [ ]
element.getAttribute()
. Returns the value of a specified attribute on the element. - [x]
element.getBoundingClientRect()
. Returns the size of an element and its position relative to the viewport. - [ ]
element.getClientRects()
. Returns a collection ofDOMRect
objects that indicate the bounding rectangles for each CSS border box in a client. - [ ]
element.hasAttribute()
. Returns a Boolean value indicating whether the specified element has the specified attribute or not. - [ ]
element.hasPointerCapture()
. Checks whether the element on which it is invoked has pointer capture for the pointer identified by the given pointer ID. - [ ]
element.scroll()
. Scrolls to a particular set of coordinates inside a given element. (Note that this would be async in React Native.) - [ ]
element.scrollBy()
. Scrolls an element by the given amount. (Note that this would be async in React Native.) - [ ]
element.scrollIntoView()
. Scrolls the page until the element gets into the view. (Note that this would be async in React Native.) - [ ]
element.scrollTo()
. Alias forscroll()
. - [ ]
element.setPointerCapture()
. Used to designate a specific element as the capture target of future pointer events. Subsequent events for the pointer will be targeted at the capture element until capture is released. - [ ]
element.releasePointerCapture()
. Releases (stops) pointer capture that was previously set for a specific (PointerEvent) pointer.
Events
Listen to these events using addEventListener()
or by assigning an event handler to the equivalent component prop.
- [ ]
error
. Fired when a resource failed to load, or can't be used. For example, if a script has an execution error or an image can't be found or is invalid. Also available via theon-error
property. - [ ]
scroll
. Fired when the document view or an element has been scrolled. Also available via theon-scroll
property. - [ ]
select
. Fired when some text has been selected. Also available via theon-select
property. - [ ]
wheel
. Fired when the user rotates a wheel button on a pointing device (typically a mouse). Also available via theon-wheel
property.
Clipboard events
- [ ]
copy
. Fired when the user initiates a copy action through the browser's user interface. Also available via theon-copy
property. - [ ]
cut
. Fired when the user initiates a cut action through the browser's user interface. Also available via theon-cut
prop. - [ ]
paste
. Fired when the user initiates a paste action through the browser's user interface. Also available via theon-paste
prop.
Focus events
- [ ]
blur
. Fired when an element has lost focus. Also available via theon-blur
prop. - [ ]
focus
. Fired when an element has gained focus. Also available via theon-focus
prop. - [ ]
focusin
. Fired when an element is about to gain focus. - [ ]
focusout
. Fired when an element is about to lose focus.
Keyboard events
- [ ]
keydown
. Fired when a key is pressed. Also available via theon-keydown
prop. - [ ]
keyup
. Fired when a key is released. Also available via theon-keyup
prop.
Pointer events
- [ ]
auxclick
. Fired when a non-primary pointing device button (e.g., any mouse button other than the left button) has been pressed and released on an element. Also available via theonAuxClick
prop. - [ ]
click
. Fired when a pointing device button (e.g., a mouse's primary button) is pressed and released on a single element. Also available via theonClick
prop. - [ ]
contextmenu
. Fired when the user attempts to open a context menu. Also available via the oncontextmenu property. - [ ]
gotpointercapture
. Fired when an element captures a pointer usingsetPointerCapture()
. Also available via theon-gotpointercapture
prop. - [ ]
lostpointercapture
. Fired when a captured pointer is released. Also available via theon-lostpointercapture
prop. - [ ]
pointercancel
. Fired when a pointer event is canceled. Also available via theon-pointercancel
prop. - [ ]
pointerdown
. Fired when a pointer becomes active. Also available via theon-pointerdown
prop. - [ ]
pointerenter
. Fired when a pointer is moved into the hit test boundaries of an element or one of its descendants. Also available via theon-pointerenter
prop. - [ ]
pointerleave
. Fired when a pointer is moved out of the hit test boundaries of an element. Also available via theon-pointerleave
prop. - [ ]
pointermove
. Fired when a pointer changes coordinates. Also available via theon-pointermove
prop. - [ ]
pointerout
. Fired when a pointer is moved out of the hit test boundaries of an element (among other reasons). Also available via theon-pointerout
prop. - [ ]
pointerover
. Fired when a pointer is moved into an element's hit test boundaries. Also available via theon-pointerover
prop. - [ ]
pointerrawupdate
. Fired when a pointer changes any properties that don't firepointerdown
orpointerup
events. - [ ]
pointerup
. Fired when a pointer is no longer active. Also available via theon-pointerup
prop.
HTMLElement
The HTMLElement interface represents any HTML element. Some elements directly implement this interface, while others implement it via an interface that inherits it.
Properties
Inherits properties from its parent, Element, and implements those from DocumentAndElementEventHandlers, GlobalEventHandlers, and TouchEventHandlers.
- [ ]
HTMLElement.hidden
[Read only]. A boolean value indicating if the element is hidden or not. - [x]
HTMLElement.offsetHeight
[Read only]. Returns a double containing the height of an element, relative to the layout. - [x]
HTMLElement.offsetLeft
[Read only]. Returns a double, the distance from this element's left border to its offsetParent's left border. - [x]
HTMLElement.offsetParent
[Read only]. Returns a Element that is the element from which all offset calculations are currently computed. - [x]
HTMLElement.offsetTop
[Read only]. Returns a double, the distance from this element's top border to its offsetParent's top border. - [x]
HTMLElement.offsetWidth
[Read only]. Returns a double containing the width of an element, relative to the layout.
Methods
Inherits methods from its parent, Element, and implements those from DocumentAndElementEventHandlers, GlobalEventHandlers, and TouchEventHandlers.
- [x]
HTMLElement.blur()
. Removes keyboard focus from the currently focused element. - [ ]
HTMLElement.click()
. Sends a mouse click event to the element. - [ ]
HTMLElement.focus(options)
. Makes the element the current keyboard focus.
Events
Listen to these events using addEventListener()
or by assigning an event listener to the equivalent component prop.
- [ ]
beforeinput
. Fired when the value of an<input>
,<select>
, or<textarea>
element is about to be modified. - [ ]
change
. Fired when the value of an<input>
,<select>
, or<textarea>
element has been changed and committed by the user. Unlike the input event, the change event is not necessarily fired for each alteration to an element's value. Also available via theon-change
property. - [ ]
input
. Fired when the value of an<input>
,<select>
, or<textarea>
element has been changed. Also available via theon-input
property.
HTMLDialogElement
The HTMLDialogElement interface represents an HTML <dialog>
element, providing the properties and methods used to manipulate image elements.
Properties
Inherits properties from its parent, HTMLElement.
- [ ]
HTMLDialogElement.open
. A boolean value representing the state of the open HTML attribute. true means it is set, and therefore the dialog is shown. false means it not set, and therefore the dialog is not shown. - [ ]
HTMLDialogElement.returnValue
. A string representing the returnValue of the dialog.
Methods
Inherits properties from its parent, HTMLElement.
- [ ]
HTMLDialogElement.close()
. Closes the dialog. An optional string may be passed as an argument, updating the returnValue of the dialog. - [ ]
HTMLDialogElement.show()
.Displays the dialog modelessly, i.e. still allowing interaction with content outside of the dialog. - [ ]
HTMLDialogElement.showModal()
. Displays the dialog as a modal, over the top of any other dialogs that might be present. Everything outside the dialog are inert with interactions outside the dialog being blocked.
Events
- [ ]
cancel
. Also available via theon-cancel
prop. - [ ]
close
. Also available via theon-close
prop.
HTMLImageElement
The HTMLImageElement interface represents an HTML <img>
element, providing the properties and methods used to manipulate image elements.
Properties
Inherits properties from its parent, HTMLElement.
- [ ]
HTMLImageElement.complete
[Read only]. Returns a boolean value that is true if the browser has finished fetching the image, whether successful or not. That means this value is also true if the image has no src value indicating an image to load. - [ ]
HTMLImageElement.currentSrc
[Read only]. Returns a USVString representing the URL from which the currently displayed image was loaded. This may change as the image is adjusted due to changing conditions, as directed by any media queries which are in place. - [ ]
HTMLImageElement.naturalHeight
[Read only]. Returns an integer value representing the intrinsic height of the image in CSS pixels, if it is available; else, it shows 0. This is the height the image would be if it were rendered at its natural full size. - [ ]
HTMLImageElement.naturalWidth
[Read only]. An integer value representing the intrinsic width of the image in CSS pixels, if it is available; otherwise, it will show 0. This is the width the image would be if it were rendered at its natural full size.
Events
- [ ]
error
. Also available via theon-error
prop. - [ ]
load
. Also available via theon-load
prop.
HTMLInputElement
The HTMLInputElement interface provides special properties and methods for manipulating the options, layout, and presentation of <input>
elements.
Properties
Properties that apply only to visible elements containing text or numbers
- [ ]
disabled
. - [ ]
selectionEnd
. unsigned long: Returns / Sets the end index of the selected text. When there's no selection, this returns the offset of the character immediately following the current text input cursor position. - [ ]
selectionStart
. unsigned long: Returns / Sets the beginning index of the selected text. When nothing is selected, this returns the position of the text input cursor (caret) inside of the element. (Resolves issue #35616.) - [ ]
selectionDirection
. string: Returns / Sets the direction in which selection occurred. Possible values are: forward (the selection was performed in the start-to-end direction of the current locale), backward (the opposite direction) or none (the direction is unknown). - [ ]
value
. string: Returns / Sets the current value of the control. If the user enters a value different from the value expected, this may return an empty string.
Methods
- [ ]
select()
. Selects all the text in the input element, and focuses it so the user can subsequently replace all of its content. - [ ]
setSelectionRange()
. Selects a range of text in the input element (but does not focus it). - [ ]
showPicker()
. Shows a browser picker for date, time, color, and files.
Events
Listen to these events using addEventListener()
or by assigning an event listener to the oneventname property of this interface:
- [ ]
invalid
. Fired when an element does not satisfy its constraints during constraint validation. Also available via theon-invalid
prop. - [ ]
select
event. Fired when some text has been selected. - [ ]
selectionchange
event. Fires when the text selection in a<input>
element has been changed. Also available via theon-selectionchange
prop.
HTMLTextAreaElement
The HTMLInputElement interface provides special properties and methods for manipulating the options, layout, and presentation of <input>
elements.
Properties
- [ ]
disabled
. - [ ]
selectionEnd
. unsigned long: Returns / Sets the end index of the selected text. When there's no selection, this returns the offset of the character immediately following the current text input cursor position. - [ ]
selectionStart
. unsigned long: Returns / Sets the beginning index of the selected text. When nothing is selected, this returns the position of the text input cursor (caret) inside of the<textarea>
element. - [ ]
selectionDirection
. string: Returns / Sets the direction in which selection occurred. Possible values are: forward (the selection was performed in the start-to-end direction of the current locale), backward (the opposite direction) or none (the direction is unknown). - [ ]
value
. string: Returns / Sets the current value of the control. If the user enters a value different from the value expected, this may return an empty string.
Methods
- [ ]
select()
. Selects all the text in the input element, and focuses it so the user can subsequently replace all of its content. - [ ]
setSelectionRange()
. Selects a range of text in the input element (but does not focus it).
Events
Listen to these events using addEventListener()
or using the equivalent prop name.
- [ ]
select
event. Fired when some text has been selected. - [ ]
selectionchange
event. Fires when the text selection in a<input>
element has been changed. Also available via theon-selectionchange
prop.
Components API (React DOM subset)
Support a subset of React DOM components directly. Exported as an html
object, i.e., html.div
, html.a
, etc.
This would provide a built-in declarative API for cross-platform elements in React Native. All unknown tags default to <span>
layout. All known-but-unimplemented block-level tags default to <div>
layout.
Sections
Content sectioning elements allow you to organize the document content into logical pieces. Use the sectioning elements to create a broad outline for your page content, including header and footer navigation, and heading elements to identify sections of content.
- [ ]
article
is equivalent toView
withdisplay:block
layout androle="article"
. - [ ]
aside
. - [ ]
dialog
- [ ]
div
is equivalent toView
withdisplay:block
layout. - [ ]
footer
. - [ ]
h1-6
. - [ ]
header
. - [ ]
main
. - [ ]
nav
. - [ ]
section
is equivalent toView
withdisplay:block
layout androle="section"
.
Text
Text content elements organize blocks or sections of content. Important for accessibility, these elements identify the purpose or structure of that content.
Inline text
Inline text semantics define the meaning, structure, or style of a word, line, or any arbitrary piece of text.
- [ ]
a
. - [ ]
b
. - [ ]
bdi
. - [ ]
bdo
. - [ ]
code
. - [ ]
em
. - [ ]
span
is equivalent toText
withdisplay:inline
layout. - [ ]
strong
. - [ ]
sub
. - [ ]
sup
.
Media
- [ ]
img
is similar toImage
without advanced loader configuration.
Forms
Elements which can be used together to create forms which the user can fill out and submit. There's a great deal of further information about this available in the HTML forms guide.
- [ ]
button
. - [ ]
input
is equivalent toTextInput
withinputMode
corresponding totype
. Expanding input type support to number, datetime, color, etc., resolves proposal #510. - [ ]
label
. - [ ]
optgroup
. - [ ]
option
. - [ ]
progress
. - [ ]
select
. - [ ]
textarea
is equivalent toTextInput
withmultiline={true}
.
Props API (React DOM subset)
Support a subset of React DOM props.
Common props
Props supported on all elements.
Supporting the ARIA 1.2 interface would allow more ARIA-targeting React DOM libraries to work on React Native, and would provide a well defined cross-platform target for equivalent native features (e.g., React Native for Windows needing heading levels, set size, etc.)
- [ ]
autoFocus
. - [ ]
aria-activedescendant
. - [ ]
aria-atomic
. - [ ]
aria-autocomplete
. - [x]
aria-busy
is the same asaccessibilityState.busy
. - [x]
aria-checked
is the same asaccessibilityState.checked
. - [ ]
aria-colcount
. - [ ]
aria-colindex
. - [ ]
aria-colindextext
- [ ]
aria-colspan
. - [ ]
aria-controls
- [ ]
aria-current
- [ ]
aria-describedby
is the same asaccessibilityDescribedBy
(Desktop). - [ ] (
aria-description
) - [ ]
aria-details
. - [x]
aria-disabled
is the same asaccessibilityState.disabled
. - [ ]
aria-errormessage
is similar toaccessibilityErrorMessage
. - [x]
aria-expanded
is the same asaccessibilityState.expanded
. - [ ]
aria-flowto
. - [ ]
aria-haspopup
. - [x]
aria-hidden
is the same asaccessibilityElementsHidden
(iOS) /importantforAccessibility
(Android). - [ ]
aria-invalid
. - [ ]
aria-keyshortcuts
. - [x]
aria-label
is the same asaccessibilityLabel
. - [x]
aria-labelledby
is the same asaccessibilityLabelledBy
(Android, Desktop). - [ ]
aria-level
is the same asaccessibilityLevel
(Desktop). - [x]
aria-live
is the same asaccessibilityLiveRegion
with'off'
value changed to'none'
(Android). - [x]
aria-modal
is the same asaccessibilityViewIsModal
(iOS). - [ ]
aria-multiline
. - [ ]
aria-multiselectable
. - [ ]
aria-orientation
. - [ ]
aria-owns
. - [ ]
aria-placeholder
. - [ ]
aria-posinset
is the same asaccessibilityPosInSet
(Desktop) - [ ]
aria-pressed
. - [ ]
aria-readonly
. - [ ]
aria-required
. - [ ]
aria-roledescription
. - [ ]
aria-rowcount
. - [ ]
aria-rowindex
. - [ ]
aria-rowindextext
. - [ ]
aria-rowspan
. - [x]
aria-selected
is the same asaccessibilityState.selected
. - [ ]
aria-setsize
is the same asaccessibilitySetSize
(Desktop). - [ ]
aria-sort
. - [x]
aria-valuemax
is the same asaccessibilityValue.max
. - [x]
aria-valuemin
is the same asaccessibilityValue.min
. - [x]
aria-valuenow
is the same asaccessibilityValue.now
. - [x]
aria-valuetext
is the same asaccessibilityValue.text
. - [ ] (
data-*
props. Resolves proposal #323) - [ ]
dir
configures element's layout / writing direction. - [ ]
elementTiming
. - [ ]
lang
identifies the language of the text content. - [ ]
hidden
is the same asdisplay:'none'
. - [x]
id
is the same asnativeID
. - [ ]
inert
. - [x]
role
is the same asaccessibilityRole
(with support for web values). - [x]
style
is unchanged from existing React Native. - [x]
tabIndex
is the same asfocusable
(Android, Desktop). Add support for0
and-1
values only.
Common events
Events supported on all elements.
Each event handler should receive a W3C event object of the corresponding type, not a synthetic event. These props are merely conveniences and otherwise identical to using the EventTarget
API mentioned later on. Note that no capture phase props are included, as listening to the capture phase (as well as custom events) can be handled with the EventTarget
API. (Alternatively, we introduce oncapture-click
, etc.)
To avoid breaking changes in React Native and React DOM, it may be preferrable to create new event prop names that receive events as specified by web standards, e.g., on-click
, on-pointerup
.
Resolves proposal #492.
- [ ]
on-auxclick
is aPointerEvent
for auxillary clicks. - [ ]
on-blur
. - [ ]
on-click
is aPointerEvent
for primary clicks. - [ ]
on-contextmenu
is aPointerEvent
for context clicks / long press. - [ ]
on-copy
. - [ ]
on-cut
. - [ ]
on-focus
. - [ ]
on-gotpointercapture
. - [ ]
on-lostpointercapture
. - [ ]
on-paste
. - [ ]
on-pointercancel
. - [ ]
on-pointerdown
. - [ ]
on-pointerenter
. - [ ]
on-pointerleave
. - [ ]
on-pointermove
. - [ ]
on-pointerout
. - [ ]
on-pointerover
. - [ ]
on-pointerup
. - [ ]
on-keydown
. - [ ]
on-keyup
.
<a>
props
Additional props for <a>
.
- [ ]
download
. - [ ]
href
. Clicking will always attempt to open the URL in a browser, mail client, etc. Use ofe.preventDefault()
in an event handler will disable this behavior and allow custom routing logic to handle the interaction. - [ ]
referredPolicy
. - [ ]
rel
. - [ ]
target
.
<dailog>
props
Additional props for <dialog>
.
- [ ]
open
.
<img>
props
Additional props for <img>
.
- [x]
alt
prop for alternative text support. - [x]
crossOrigin
is equivalent to setting the relevantHeader
in thesource
object. Declaratively configure cross-origin permissions for loaded images. - [ ]
decoding
. - [ ]
draggable
. - [ ]
fetchPriority
. - [x]
height
is the same assource.height
. - [ ]
loading
. - [x]
referrerPolicy
is equivalent to setting the relevantHeader
in thesource
object. Declaratively configure referrer policy. - [x]
src
is the same assource.uri
. - [x]
srcSet
is the same as setting asource
array withuri
andscale
properties defined. - [x]
width
is the same assource.width
.
<img>
events
Additional events for <img>
.
- [ ]
on-error
- [ ]
on-load
<input>
props
Additional props for <input>
.
- [x]
autoComplete
is the same as mapped values for existingautoComplete
(Android) andtextContentType
(iOS). - [ ]
disabled
. - [x]
enterKeyHint
is the same as mapped values forreturnKeyType
. - [x]
inputMode
is the same as mapped values forkeyboardType
.- [x]
inputMode === 'decimal'
is the same askeyboardType = 'decimal-pad'
. - [x]
inputMode === 'email'
is the same askeyboardType = 'email-address'
. - [x]
inputMode === 'none'
is the same asshowSoftInputOnFocus = false
. - [x]
inputMode === 'numeric'
is the same askeyboardType = 'numeric'
. - [x]
inputMode === 'search'
is the same askeyboardType = 'search'
. - [x]
inputMode === 'tel'
is the same askeyboardType = 'phone-pad'
. - [x]
inputMode === 'url'
is the same askeyboardType = 'url'
.
- [x]
- [ ]
max
. - [x]
maxLength
. - [ ]
min
. - [ ]
minLength
. - [ ]
placeholder
. - [x]
readOnly
is the same as inverseeditable
. - [ ]
required
. - [ ]
spellCheck
. - [ ]
type
. - [ ]
value
.
<textarea>
props
Additional props for <textarea>
.
- [x]
autoComplete
is the same as mapped values for existingautoComplete
(Android) andtextContentType
(iOS). - [ ]
disabled
. - [x]
enterKeyHint
is the same as mapped values forreturnKeyType
. - [x]
inputMode
is the same as mapped values forkeyboardType
.- [x]
inputMode === 'decimal'
is the same askeyboardType = 'decimal-pad'
. - [x]
inputMode === 'email'
is the same askeyboardType = 'email-address'
. - [x]
inputMode === 'none'
is the same asshowSoftInputOnFocus = false
. - [x]
inputMode === 'numeric'
is the same askeyboardType = 'numeric'
. - [x]
inputMode === 'search'
is the same askeyboardType = 'search'
. - [x]
inputMode === 'tel'
is the same askeyboardType = 'phone-pad'
. - [x]
inputMode === 'url'
is the same askeyboardType = 'url'
.
- [x]
- [ ]
max
. - [x]
maxLength
. - [ ]
min
. - [ ]
minLength
. - [ ]
placeholder
. - [x]
readOnly
is the same as inverseeditable
. - [ ]
required
. - [x]
rows
is the same asnumberOfLines
. - [ ]
spellCheck
. - [ ]
value
.
<input>
and <textarea>
events
Additional events for <input>
and <textarea>
.
- [ ]
on-beforeinput
- [ ]
on-change
- [ ]
on-input
- [ ]
on-invalid
- [ ]
on-select
- [ ]
on-selectionchange
Styles API (CSS subset)
Significantly expanded styling capabilities to cover more of the features that are heavily relied upon by web engineers. Styles are used with the css.create()
function and are passed to the style
prop on elements.
css()
function
Styles must be wrapped in css()
.
const styles = css.create({
foo: {
width: 100
},
bar: (color) => ({
color
})
});
<div style={styles.foo} />
<div style={styles.bar(props.color)} />
CSS Compatibility
Existing properties that can be adjusted to align with the CSS spec.
- [x]
aspectRatio
. Support string values, i.e.,'16 / 9'
, to align with CSS. - [x]
borderRadius
. Support percentage values to align with CSS. - [x]
fontVariant
support space-separated string values to align with CSS. - [x]
fontWeight
support number values to align with React DOM / CSS. - [x]
objectFit
is equivalent toresizeMode
for<Image>
. - [x]
pointerEvents
is equivalent topointerEvents
prop. - [ ]
position
. Support forfixed
andsticky
values. - [x]
transform
. Support using string values to set transforms. - [x]
verticalAlign
is equivalent totextAlignVertical
. - [x]
userSelect
. Equivalent to usingselectable
prop on<Text>
.
Existing logical properties that can be adjusted to adopt the CSS standard names. In addition, React Native will need to add native support for subtree-level writing direction controls. Setting the dir
prop (or direction
style) to ltr
or rtl
on an element should alter the way logical properties are resolved in the subtree.
- [ ] (
direction
. But it is not recommended for most use cases on web.) - [x]
borderEndEndRadius
is equivalent toborderBottomEndRadius
. - [x]
borderEndStartRadius
is equivalent toborderBottomStartRadius
. - [x]
borderStartEndRadius
is equivalent toborderTopEndRadius
. - [x]
borderStartStartRadius
is equivalent toborderTopStartRadius
. - [ ]
borderBlockColor
is equivalent toborderTopColor
&borderBottomColor
. - [ ]
borderBlockEndColor
is equivalent toborderBottomColor
. - [ ]
borderBlockStartColor
is equivalent toborderTopColor
. - [ ]
borderInlineColor
is equivalent toborderEndColor
&borderStartColor
. - [ ]
borderInlineEndColor
is equivalent toborderEndColor
. - [ ]
borderInlineStartColor
is equivalent toborderStartColor
. - [ ]
borderBlockStyle
is equivalent toborderTopStyle
&borderBottomStyle
. - [ ]
borderBlockEndStyle
is equivalent toborderBottomStyle
. - [ ]
borderBlockStartStyle
is equivalent toborderTopStyle
. - [ ]
borderInlineStyle
is equivalent toborderEndStyle
&borderStartStyle
. - [ ]
borderInlineEndStyle
is equivalent toborderEndStyle
. - [ ]
borderInlineStartStyle
is equivalent toborderStartStyle
. - [ ]
borderBlockWidth
is equivalent toborderTopWidth
&borderBottomWidth
. - [ ]
borderBlockEndWidth
is equivalent toborderBottomWidth
. - [ ]
borderBlockStartWidth
is equivalent toborderTopWidth
. - [ ]
borderInlineWidth
is equivalent toborderEndWidth
&borderStartWidth
. - [ ]
borderInlineEndWidth
is equivalent toborderEndWidth
. - [ ]
borderInlineStartWidth
is equivalent toborderStartWidth
. - [x]
marginInlineStart
is equivalent tomarginStart
. - [x]
marginInlineEnd
is equivalent tomarginEnd
. - [x]
marginBlockStart
is equivalent tomarginTop
. - [x]
marginBlockEnd
is equivalent tomarginBottom
. - [x]
marginBlock
is equivalent tomarginVertical
. - [x]
marginInline
is equivalent tomarginHorizontal
. - [x]
paddingInlineStart
is equivalent topaddingStart
. - [x]
paddingInlineEnd
is equivalent topaddingEnd
. - [x]
paddingBlockStart
is equivalent topaddingTop
. - [x]
paddingBlockEnd
is equivalent topaddingBottom
. - [x]
paddingBlock
is equivalent topaddingVertical
. - [x]
paddingInline
is equivalent topaddingHorizontal
. - [x]
inset
is equivalent totop
&bottom
&right
&left
. - [x]
insetBlock
is equivalent totop
&bottom
. - [x]
insetBlockEnd
is equivalent tobottom
. - [x]
insetBlockStart
is equivalent totop
. - [x]
insetInline
is equivalent toright
&left
. - [x]
insetInlineEnd
is equivalent toright
orleft
. - [x]
insetInlineStart
is equivalent toright
orleft
. - [ ]
blockSize
is equivalent toheight
. - [ ]
minBlockSize
is equivalent tominHeight
. - [ ]
maxBlockSize
is equivalent tomaxHeight
. - [ ]
inlineSize
is equivalent towidth
. - [ ]
minInlineSize
is equivalent tominWidth
. - [ ]
maxInlineSize
is equivalent tomaxWidtht
.
CSS Animations
Support declarative keyframes and animations that can be optimized on the native side and avoid the need for Animated
. Consider dispatching the corresponding W3C animation events too. See animation).
- [ ]
animationDelay
- [ ]
animationDirection
- [ ]
animationDuration
- [ ]
animationFillMode
- [ ]
animationIterationCount
- [ ]
animationName
- [ ]
animationPlayState
- [ ]
animationTimingFunction
TBD: the relationship between animationName
(or equivalent) and the API used to define Animation keyframes.
CSS Colors
Support CSS 4 Colors, possibly by using Colorjs.io or implementing a native equivalent.
CSS Container Queries
Prepare for CSS Container Queries.
CSS Custom Properties
Support CSS custom property syntax --variable-name
. This could be shimmed in user-space on top of the existing StyleSheet
API, with a React Context used to provide variables and values to a subtree.
CSS Filters
Support declarative filters as used by the CSS filter style.
- [ ]
blur()
- [ ]
brightness()
- [ ]
contrast()
- [ ]
drop-shadow()
- [ ]
grayscale()
- [ ]
hue-rotate()
- [ ]
invert()
- [ ]
opacity()
- [ ]
saturate()
- [ ]
sepia()
CSS Functions
CSS Lengths
Support major units where supported by CSS.
CSS Media Queries
Support CSS Media Queries.
Although Media Queries are not a preferred long-term solution for responsive design, Container Queries are not yet widely supported by browsers. The dimensional Media Queries could be shimmed in user-space on top of the existing StyleSheet
API.
Logical operators not
, and
, or
, only
.
- [ ]
aspect-ratio
- [ ]
forced-colors
- [ ]
height
- [ ]
inverted-colors
- [ ]
orientation
- [ ]
prefers-color-scheme
- [ ]
prefers-contrast
- [ ]
prefers-reduced-motion
- [ ]
resolution
- [ ]
width
Proposed syntax:
// Either
css.create({
position: 'absolute',
'@media (max-width: 600px)': {
position: 'sticky',
}
});
// Or
css.create({
position: {
default: 'absolute',
'@media (max-width: 600px)': 'sticky',
}
});
The benefit of the latter is clearer expectations (and greater constraints) about when properties are overridden. For example, it's not as clear what the value for position
would be in the following:
css.create(
{
position: 'absolute',
'@media (max-width: 600px)': {
position: 'sticky',
}
},
{
position: 'relative',
}
);
// {
// position: relative,
// '@media (max-width: 600px)': {
// position: 'sticky',
// }
// }
Whereas in the next example we can set expectations that a property and any previous conditions are completed overridden by any declaration that later modifies the property in any way.
css.create(
{
position: {
default: 'absolute',
'@media (max-width: 600px)': 'sticky'
}
},
{
position: {
default: 'relative',
}
}
);
// {
// position: relative
// }
Consideration is also required to determine what the final value of a property is when Media Query conditions overlap, i.e., which media features and which conditions (e.g., multiple width
conditions) take priority over others.
CSS Properties and Values
Miscellaneous CSS properties and values that should be supported on native.
- [ ]
backgroundImage
. Add support for setting background images viaurl()
. Stretch: support CSS gradients, e.g.,linear-gradient()
. - [ ]
backgroundOrigin
. - [ ]
backgroundPosition
. - [ ]
backgroundRepeat
. - [ ]
backgroundSize
. - [ ]
boxSizing
. - [ ]
boxShadow
. Add native support for CSS box shadows to replace buggy, iOS-specificshadow*
styles. Resolves issue #26110 and multiple other issues. - [ ]
caretColor
. - [ ]
clipPath
. - [ ]
display
values ofblock
,contents
(resolves proposal #348),inline
,inline-block
,inline-flex
, andgrid
. - [ ]
float
. - [ ]
justifySelf
. Sets the way a box is justified inside its alignment container along the appropriate axis. - [ ] (
lineClamp
is equivalent tonumberOfLines
prop on<Text>
components). - [ ]
objectPosition
. - [ ]
overflowBlock
. - [ ]
overflowInline
. - [ ]
overflowX
. - [ ]
overflowY
. - [ ]
placeContent
. Shorthand for settingalignContent
andjustifyContent
. Value is an enum ofbaseline
,first baseline
,last baseline
,center
,end
,flex-start
,flex-end
,left
,right
,safe
,space-around
,space-between
,space-evenly
,start
,stretch
,unsafe
. Note thatspace-evenly
should also be supported foralignContent
andjustifyContent
. - [ ]
placeItems
. Shorthand for settingalignItems
andjustifyItems
. Value is an enum ofauto
,baseline
,first baseline
,last baseline
,center
,end
,flex-start
,flex-end
,left
,right
,start
,stretch
. - [ ]
placeSelf
. Shorthand for settingalignSelf
andjustifySelf
. Value is an enum ofauto
,baseline
,first baseline
,last baseline
,center
,end
,flex-start
,flex-end
,left
,normal
,right
,start
,stretch
. - [ ]
rotate
. - [ ]
scale
. - [ ]
scrollSnap*
- [ ]
textShadow
. Add native support for CSS text shadows. Resolves issue #26110. - [ ]
touchAction
. - [x]
transformOrigin
. - [ ]
transformStyle
. Resolves proposal #425. - [ ]
translate
. - [ ]
visibility
. Add support for visually hiding elements. - [ ]
whiteSpace
.
CSS Transitions
Support declarative transitions that can be optimized on the native side and avoid the need for Animated
. Consider dispatching the corresponding W3C transition events too.
- [ ]
transitionDelay
- [ ]
transitionDuration
- [ ]
transitionProperty
- [ ]
transitionTimingFunction
I think this is a great overview of how we can incrementally move RN closer to web standards and reduce API fragmentation.
When it comes to implementing this though, let's look at how we can introduce a new RN abstraction to efficiently introduce aliases and mapping functions, so we can avoid overhead and breakages from doing this.
Recording feedback left on Twitter:
Expo
We at 𝝠 @expo ❤️ this! Supporting standards means: ➜ Backwards compatibility ➜ Universal codebases ➜ Reliable updates
https://twitter.com/Baconbrix/status/1555953435576606720
NativeBase
This RFC is a giant leap in our efforts to create universal applications with a single codebase. The API of React Native was created for mobile platforms. When used in web apps it created limitations and a lack of synergy. The new RFC removes the old shackles
https://twitter.com/nativebase/status/1556961633859436545
Glad to see this effort! When you're coming from React to React Native and wanting to build universal apps (web included) you feel like you're taking a step back from what you could do with React on the web. It shouldn't be. This is also a step forward into making life easier for lib authors that want to support both React and React Native. There's a lot of progress made in building UI libs and design systems that focus on accessibility, customisation etc. but that's mostly React only for now. Would be awesome for RN to benefit more from this as well.
This is really a great thing to see indeed, particularly needed too. In detail, I feel that this discussion I opened over react-native-web https://github.com/necolas/react-native-web/discussions/2289 fits really well in the effort of reducing fragmentation between the web and the native platforms.
Now, it might also be "by design", but even having just some info about it would be awesome.
PS: I don't want this comment to grab the attention from the whole fragmentation thing as a mere issue/help request. But a discussion could be opened, imho, about the idea of making all the views with overflow: 'hidden' set by default, since it might be something that could reduce friction and fragmentation between the two plats.
Awesome stuff, this is really exciting work, thanks for driving it @necolas!
This proposal is really great and I think it'll make React Native feel a lot more fluid.
I am concerned that the styling changes will really struggle to be developed due to the lack of ownership on Yoga -- I get a lot of questions from the community about this, would love some more clarity on Meta's plan to unblock developers who are trying to contribute more styling functionality.
Apple and Google's versions of React Native offer a lot of built-in style properties, which feels really expressive and powerful to use. I would love to see React Native catch up and offer a wider variety of cross-platform styles in the core primitives.
Suggestion: In addition to the new feature additions, it would also be nice if Android failed more gracefully when invalid or unsupported style properties are used, currently Android runtimes tend to throw fatal errors when typos are used repeatedly.
I see users also perform generalized filtering in runtime code to ensure the upstream Android components don't fail. Here's an example of such code:
Example
const WEB_STYLES = [
"backdropFilter",
"animationDelay",
"animationDirection",
"animationDuration",
"animationFillMode",
"animationName",
"animationIterationCount",
"animationPlayState",
"animationTimingFunction",
"backgroundAttachment",
"backgroundBlendMode",
"backgroundClip",
"backgroundImage",
"backgroundOrigin",
"backgroundPosition",
"backgroundRepeat",
"backgroundSize",
"boxShadow",
"boxSizing",
"clip",
"cursor",
"filter",
"gridAutoColumns",
"gridAutoFlow",
"gridAutoRows",
"gridColumnEnd",
"gridColumnGap",
"gridColumnStart",
"gridRowEnd",
"gridRowGap",
"gridRowStart",
"gridTemplateColumns",
"gridTemplateRows",
"gridTemplateAreas",
"outline",
"outlineColor",
"overflowX",
"overflowY",
"overscrollBehavior",
"overscrollBehaviorX",
"overscrollBehaviorY",
"perspective",
"perspectiveOrigin",
"touchAction",
"transformOrigin",
"transitionDelay",
"transitionDuration",
"transitionProperty",
"transitionTimingFunction",
"userSelect",
"visibility",
"willChange",
];
function filterPlatformStyles(style) {
if (Platform.OS !== "web") {
return Object.fromEntries(
Object.entries(style).filter(([k]) => !WEB_STYLES.includes(k))
);
}
return style;
}
First 2 umbrella issues for the simpler tasks mentioned in this proposal:
https://github.com/facebook/react-native/issues/34424 https://github.com/facebook/react-native/issues/34425
CSS Lengths
-
should
px
be mapped to a physical pixel or alias tosp
anddp
unit on native?px
on the web doesn't map to the physical pixel so not sure what should be the behaviour. I like that native usessp
for text which is kind of likerem
but relative to device font settings. I might be wrong but I thinkpx
on web is kind of similar todp
on native? -
em
would be tricky becauseView
doesn't have font-size andText
cannot have children other thanText
nodes. -
+1 for v*. Supporting resizing would need native changes though or can be solved by asking to force rerender for now.
I'd also suggest adding an inset
property (top/right/bottom/left shorthand) to RN StyleSheets to match CSS.
I'd love to see this feature. We've got at least six places we're writing out top: 0, bottom: 0, ....
it's very verbose for both reading and writing.
Might be a silly question, but would it make sense to work with an existing css engine that a browser uses, or to build out the css spec directly?
Working on a design system over here, and one of the big pain points for us is the need to copy/rewrite styles to support web and native separately.
It would be amazing if we could say, share css between web and native code.
Just a quick add, what about RNs TextInput
setSelection
, while on the web its setSelectionRange
?
As fetch
is quickly becoming the standard for both web & Node.js, should React Native resolve the documented issues with its fetch
implementation? https://reactnative.dev/docs/network#known-issues-with-fetch-and-cookie-based-authentication.
The workaround mentioned was removed in 0.60
with no other solution in place
The RFC has been significantly updated to include a more complete "Strict DOM" API proposal that builds on the earlier version published a few months ago, and incorporates feedback shared in this thread. Please review and continue to provide feedback! Thanks
In the spirit of supporting onBeforeInput
on TextInput
, it would be nice to also support event.preventDefault()
for <TextInput />
's onChange
event.
Since you currently can't prevent the change event, inputs that use any formatting (such as for numbers/currency) experience a jarring flicker before showing the formatted value. Web inputs don't experience this flicker.
I'm hoping that this will be possible with the new React Native architecture, since it would require a synchronous event called from JS.
should React Native resolve the documented issues with its fetch implementation?
@marklawlor: that sounds like a feature request / bug report better served by opening an issue on the react-native repo.
it would be nice to also support
event.preventDefault()
@nandorojo: That's implied by the proposal to adopt W3C event types
it would also be nice if Android failed more gracefully when invalid or unsupported style properties are used, currently Android runtimes tend to throw fatal errors
@EvanBacon: added a note about this to the RFC
When working on a library, it's useful to be able to accept a regular-style prop and flatten it to pass it down to the underlying native component.
@satya164: added the StyleSheet(...styles)
API to the RFC, which returns native props and internalizes the flattening/transforming on input styles. This API already exists in react-native-web.
Props API discussion topics
A few open questions / complexities around the "Props API" proposal. Which parts need more thought? How might we take steps to create native implementations of these APIs?
ARIA props
There is a large list of unimplemented ARIA props with no current equivalent in React Native. Previously there has been input from native accessibility experts that Android and iOS have different and incompatible accessibility paradigms. So there is some question as to if/how to implement ARIA in React Native. In the short term, perhaps we add them all as supported types but they do nothing. In the long term, we should access the viability of implementing Web/ARIA accessibility paradigms directly in native as the browser does.
Localized layout props
The dir
and lang
props are part of localizing content and layout. The dir
prop should alter the writing direction of its subtree. The dir="auto"
value would need to be implemented by inferring writing direction of text, and should be limited to text elements. The ltr/rtl values should change how Yoga layout renders the subtree, accounting for logical CSS properties and values too.
Hidden and inert elements
The hidden
prop should function like display:none
. Whereas inert
prevents user interaction with the subtree.
Event props
Event props in React Native currently accept functions that receive events with all their data hanging off e.nativeEvent
. In React DOM, e.nativeEvent
is the underlying W3C event, but all the properties of that event are copied to be properties of e
alongside some React-specific functions like isDefaultPrevented()
. Furthermore, most React Native event objects attached to nativeEvent
are not compatible with W3C event types. As such, events types between React DOM and React Native are currently incompatible and libraries like React Native for Web have to do a bunch of work to transform one event type into the other. React DOM is also in the process of migrating away from the synthetic event system.
It may be too difficult to migrate the existing onEvent
props in React Native without causing breaking changes. Even doing so in React DOM could take years. Therefore, we could adopt a new prop convention for events that conform to W3C events, which is on-{name}
. This event prop would pass the W3C event directly to the handler. No capture phase events would be supported as props, as these are rarely used and can be accessed via the EventTarget API. Instead these new event props would be convenience props for common events but otherwise indistinguishable from EventTarget use. This would support mixing-and-matching event props with imperative event handling.
Image loading
The core image component could be a lot simpler if it didn't internalize an entire image loading module. The <img>
implementation would be a chance to deliver a new component that plays well with image loading via hook. If developers need complex logic around loading, such as auth headers, these can be supplied to the loader. And loader state can be used to drive changes to an image src.
Text input handling
Although most of the additional text input handling can be worked into TextInput
, we should consider breaking this component down into textarea and input types that correspond with the division on web. The current TextInput
component has many props that only work if the input is multiline
, and doesn't support built-in date pickers, color pickers, etc.
Love this and the direction it's evolving in.
Some thoughts around future extensibility: What is the modularity story for adding new elements and host APIs (as a user or as a platform maintainer)?
I'm mainly thinking of the binary size and startup time impact of shipping unused component implementations, but also of versioning and dependency problems that may arise as we iterate on this approach.
- Should user code even be allowed to define its own implementations of built-in elements?
- Should we support Custom Elements in some way?
- How should third-party packages express their dependency on certain built-in elements or custom elements being present?
peerDependencies
? - Overall it seems like we are buying into some of the web's global namespacing problems (elements, host APIs) and I wonder if there's a middle ground that is more hygienic and modular, without losing the extremely important compatibility benefits. (Explicitly defining provided/consumed features in app/package manifest files?)
Expo have done some work on modular addition of globals to the environment. Perhaps @EvanBacon or @brentvatne can talk about what they've been doing and how it might answer some of those questions.
Should user code even be allowed to define its own implementations of built-in elements?
I'm not sure what this means. Are people already able to do that today with React Native?
Should we support Custom Elements in some way?
Not at this stage, since React DOM doesn't even support Custom Elements yet. Even for the proposal of adding div
, etc., I think those elements will need to be explicitly imported. For the internal shim all the elements are imported on an object called strict
or both web and native, e.g., <strict.div>
. Fortunately, most web elements are exactly the same apart from semantics so the number of proposed elements doesn't necessarily mean that much more code. The total size of the DOM shim on native (before we moved some of the prop transforms into RN) was only 10KB gzip.
I'm mainly thinking of the binary size and startup time impact of shipping unused component implementations
I think the current RN JS API is a lot bigger in many ways than what is proposed here. The lists and animated API alone account for about 1/3rd of the total package size for react-native-web. In general we should be moving to a more modular integration that allows a small core to be extended with APIs in use. One idea I discussed with Expo was whether a tool like Metro could understand which dependencies to include as it parses the modules, e.g., if it finds ResizeObserver
then it bundles and initializes that global on boot. Otherwise it isn't included. And developers don't have to manually manage those kinds of dependencies. It could also be a place to throw errors / warnings if the bundler knows that you're trying to use ResizeObserver
with a version of RN that can't support that API.
Will devs who target iOS and Android be able to opt out of these changes indefinitely? I've been a mobile dev for a while and have had no plans to go back to web. In my opinion making mobile APIs more web-like is not a positive. I don't wish to go back to a web API on mobile. This direction would push me toward native mobile and Flutter. Flutter is able to target web (and numerous other platforms) without requiring devs to write browser UI. Also while I know that Capacitor is completely different architecturally I wanted to point out that it already exists and has an identical API to web for people who want that.
Will devs who target iOS and Android be able to opt out of these changes indefinitely?
The proposal details a few things:
-
The existing APIs in React Native that are modelled on—or equivalent to—standard web APIs can be adjusted to match the web APIs. No change in functionality, only names and behaviour that are immediately understandable to more developers.
-
Greater cross-platform alignment of existing features in React Native makes it more obvious where additional, useful features can be added. We don't have to do design work and can get capabilities into people's hands faster. The paths for external contributors are also much clearer. Examples of that are the new logical CSS properties, flex
gap
support, and PointerEvent APIs shipped in 0.71. -
There is no mention of removing existing APIs or Components. The proposal is to export a dedicated DOM compatibility API. It would work alongside existing JS APIs. The current React Native APIs will likely be extended to include some of these new DOM-based APIs anyway, because they do exactly what we need. For example, the new architecture supports important and requested capabilities like synchronous calls into native. And to take advantage of that we want to create APIs like
getBoundingClientRect
for synchronous layout measurement. But none of this stops React Native from being used as it is today, whether that'sView
etc or backing custom React components with custom native implementations.
I've been a mobile dev for a while and have had no plans to go back to web
React Native JS APIs are already heavily based on the web. "Web the good parts" was the original vision of the creators of React Native. That's why there's so many of the exported APIs are already based on CSS and DOM APIs.
Re: alternative frameworks. We believe that React Native can deliver better performance on native than many alternatives. And we provide a level of performance, accessibility, and reach on web via React DOM.
I think a more accurate way of framing this proposal is that it is part of expanding what is possible with React Native. It's one way in which we can take advantage of the capabilities the new architecture is creating. And how we may be able to better leverage the web side of the React ecosystem. No existing capabilities in React Native are being closed off.
Big +1 from Tamagui on this. All of this looks pretty well done and will help a lot in the long term with bundle size and maintenance.
The on- events are the most odd part, both as a user and as library having to map camel to that potentially.
We're also looking for input on use cases for additional DOM traversal APIs:
Node:
- node.parentNode
- node.parentElement
- node.childNodes
- node.firstChild
- node.lastChild
- node.previousSibling
- node.nextSibling
Element:
- element.children
- element.childElementCount
- element.firstElementChild
- element.lastElementChild
- element.previousElementSibling
- element.nextElementSibling
Subscribing so hard to this RFC. Thank you @necolas
Added traversal APIs. We'll publish a separate RFC that goes into more detail on the traversal API motivation and implementation.
Expo also expressed interest in RN supporting more of the APIs from the WinterCG: https://common-min-api.proposal.wintercg.org/#index
Really nice work! 🎉
I was curious about linear-gradient()
CSS function? Would love to see that be part of React Native as well 🙈
We've been using React Native together with React Native Web for the past few years building beatgig.com. This RFC's completion would have an enormous impact.
I'm excited for so much of this, namely:
- More CSS styles (gradients, filters, mask-image, clip-path, etc.)
- IntersectionObserver
- Synchronous
e.preventDefault()
onhtml.input
, allowing formatting without flickers -
boxShadow
allowing multiple shadows - Generally using web APIs that are well-documented all over the internet
I came to RN from the web originally, and having React DOM for Native would have saved me so much time and effort. Hiring for RN would get far simpler too, as any web developer could jump right into the role.
Thanks for all the work here.
I think transformOrigin
should be added here. There’s a new PR for that by @intergalacticspacehighway at https://github.com/facebook/react-native/pull/37606 for reference
For anyone who prefers video form, @nandorojo's talk "React Native in 2030" has a section on Modern CSS here that references this RFC: https://youtu.be/dKItY_2wFH0?t=775
"The Dream" of write once, run anywhere would become substantially easier with this change, and Fernando's talk makes a pretty compelling argument.
If I'm not bad at searching, would love to see <dialog>
🙈, provided aria-modal
is already included.
Ref: