Skip to main content
Version: 0.18.0

Events

Introduction

Yew integrates with the web-sys crate and uses the events from that crate. The table below lists all of the web-sys events that are accepted in the html! macro.

You can still add a Callback for an event that is not listed in the table below, see Manual event listener.

Event Types

tip

All the event types mentioned in the following table are re-exported under yew::events. Using the types from yew::events makes it easier to ensure version compatibility than if you were to manually include web-sys as a dependency in your crate because you won't end up using a version which conflicts with the version that Yew specifies.

The event listener name is the expected name when adding an event Callback in the html macro:

use yew::{html, Callback};

html! {
<button onclick={Callback::from(|_| ())}>
// ^^^^^^^ event listener name
{ "Click me!" }
</button>
};

The event name is the listener without the "on" prefix, therefore, the onclick event listener listens for click events.

Event listener nameweb_sys Event Typestdweb Event Type
onabortEventResourceAbortEvent
onauxclickMouseEventAuxClickEvent
onblurFocusEventBlurEvent
oncancelEventUnsupported
oncanplayEventUnsupported
oncanplaythroughEventUnsupported
onchangeChangeDataChangeData
onclickMouseEventClickEvent
oncloseEventUnsupported
oncontextmenuMouseEventContextMenuEvent
oncuechangeEventUnsupported
ondblclickMouseEventDoubleClickEvent
ondragDragEventDragEvent
ondragendDragEventDragEndEvent
ondragenterDragEventDragEnterEvent
ondragexitDragEventDragExitEvent
ondragleaveDragEventDragLeaveEvent
ondragoverDragEventDragOverEvent
ondragstartDragEventDragStartEvent
ondropDragEventDragDropEvent
ondurationchangeEventUnsupported
onemptiedEventUnsupported
onendedEventUnsupported
onerrorEventResourceErrorEvent
onfocusFocusEventFocusEvent
onformdataEventUnsupported
oninputInputDataInputData
oninvalidEventUnsupported
onkeydownKeyboardEventKeyDownEvent
onkeypressKeyboardEventKeyPressEvent
onkeyupKeyboardEventKeyUpEvent
onloadEventResourceLoadEvent
onloadeddataEventUnsupported
onloadedmetadataEventUnsupported
onloadstartProgressEventLoadStartEvent
onmousedownMouseEventMouseDownEvent
onmouseenterMouseEventMouseEnterEvent
onmouseleaveMouseEventMouseLeaveEvent
onmousemoveMouseEventMouseMoveEvent
onmouseoutMouseEventMouseOutEvent
onmouseoverMouseEventMouseOverEvent
onmouseupMouseEventMouseUpEvent
onpauseEventUnsupported
onplayEventUnsupported
onplayingEventUnsupported
onprogressProgressEventProgressEvent
onratechangeEventUnsupported
onresetEventUnsupported
onresizeEventResizeEvent
onscrollEventScrollEvent
onsecuritypolicyviolationEventUnsupported
onseekedEventUnsupported
onseekingEventUnsupported
onselectEventUnsupported
onslotchangeEventSlotChangeEvent
onstalledEventUnsupported
onsubmitFocusEventSubmitEvent
onsuspendEventUnsupported
ontimeupdateEventUnsupported
ontoggleEventUnsupported
onvolumechangeEventUnsupported
onwaitingEventUnsupported
onwheelWheelEventMouseWheelEvent
oncopyEventUnsupported
oncutEventUnsupported
onpasteEventUnsupported
onanimationcancelAnimationEventUnsupported
onanimationendAnimationEventUnsupported
onanimationiterationAnimationEventUnsupported
onanimationstartAnimationEventUnsupported
ongotpointercapturePointerEventGotPointerCaptureEvent
onloadendProgressEventLoadEndEvent
onlostpointercapturePointerEventLostPointerCaptureEvent
onpointercancelPointerEventPointerCancelEvent
onpointerdownPointerEventPointerDownEvent
onpointerenterPointerEventPointerEnterEvent
onpointerleavePointerEventPointerLeaveEvent
onpointerlockchangeEventPointerLockChangeEvent
onpointerlockerrorEventPointerLockErrorEvent
onpointermovePointerEventPointerMoveEvent
onpointeroutPointerEventPointerOutEvent
onpointeroverPointerEventPointerOverEvent
onpointerupPointerEventPointerUpEvent
onselectionchangeEventSelectionChangeEvent
onselectstartEventUnsupported
onshowEventUnsupported
ontouchcancelTouchEventTouchCancel
ontouchendTouchEventTouchEnd
ontouchmoveTouchEventTouchMove
ontouchstartTouchEventTouchStart
ontransitioncancelTransitionEventUnsupported
ontransitionendTransitionEventUnsupported
ontransitionrunTransitionEventUnsupported
ontransitionstartTransitionEventUnsupported

oninput and onchange

note

In the next version of Yew oninput and onchange Callbacks will accept InputEvent and Event respectively.

Yew will be removing InputData and ChangeData as they were too restrictive and could panic in certain circumstances.

caution

You must apply the Callback to the target element even though the InputEvent/Event bubbles up, the InputData/ChangeData is expecting the "target" to also be the "currentTarget" (see the caution in Typed event target section for more).

DO NOT DO THIS
use yew::{html, ChangeData, Html, InputData};

enum Msg {
InputValue(String),
}


fn view(&self) -> Html {

let oninput = self.link.callback(|e: InputData| Msg::InputValue(e.value));
let onchange = self.link.batch_callback(|e: ChangeData| {
if let ChangeData::Value(value) = e {
Some(Msg::InputValue(value))
} else {
None
}
});

html! {
<div
// The `InputEvent` can bubble and then will read the text content
// of the div as the value in `InputData` which is not what you'd
// expect!
oninput={oninput}
// The `Event` can bubble and will cause a panic when it tries
// to create the `ChangeData` enum.
onchange={onchange}
>
{ "hi" }
<input type="text" />
</div>
}
}

oninput using InputData

Yew wraps the InputEvent in an InputData type, the InputData type also contains the current value of the element on which the oninput handler is applied.

Yew does this by trying to cast the element into an input or textarea element then calling their respective value getter method or it will get the text content of the element.

use yew::{html, Html, InputData};

enum Msg {
InputValue(String),
}


fn view(&self) -> Html {

let oninput = self.link.callback(|e: InputData| Msg::InputValue(e.value));

html! {
<div>
<input oninput={oninput} />
</div>
}
}

If you want to get the value as a number (f64) then InputData does have the event field which is the web_sys::InputEvent that you can use with the information provided in the Typed event target section and then you can call value_as_number method on the target.

onchange using ChangeData

The ChangeData type is an enum which has a variant for the three supported element types:

Variant nameVariant data typeElement type
Filesweb_sys::FileListinput with type of file
Selectweb_sys::HtmlSelectElementselect element
ValueStringtextarea or input with any type other than file

If onchange is used on any other element then the application will panic when Yew tries to convert the Event into ChangeData.

Files

When a user has made a change to the files selected by an input element with the type file.

use yew::{html, Html, ChangeData};

fn view(&self) -> Html {

let onchange = self.link.batch_callback(|e| {
if let ChangeData::Files(files) = e {
// do something with web_sys::FileList
} else {
None
}
});

html! {
<div>
<input onchange={onchange} type="file" />
</div>
}
}
tip

Use batch_callback when you want to conditionally return a value from a Callback.

see file_upload for a full example.

Select

When a user has made a change to the select element.

use yew::{html, Html, ChangeData};

fn view(&self) -> Html {

let onchange = self.link.batch_callback(|e| {
if let ChangeData::Select(select) = e {
// do something with web_sys::HtmlSelectElement
} else {
None
}
});

html! {
<div>
<select onchange={onchange}>
<option value="English">{ "Hello!" }</option>
<option value="French">{ "Bonjour!" }</option>
<option value="German">{ "Guten Tag!" }</option>
</select>
</div>
}
}

Value

When a user has made a change to a textarea or input element with any type other than file.

use yew::{html, Html, ChangeData};

fn view(&self) -> Html {

let onchange = self.link.batch_callback(|e| {
if let ChangeData::Value(value) = e {
// do something with the String value
} else {
None
}
});

html! {
<div>
<textarea onchange={onchange} />
</div>
}
}

see crm for a full example.

Typed event target

caution

In this section target (Event.target) is always referring to the element at which the event was dispatched from.

This will not always be the element at which the Callback is placed, that is the Event.currentTarget

In event Callbacks you may want to get the target of that event. For example, on the input event you may want to update some internal state.

In Yew getting the target element in the correct type can be done in a couple of ways and we will go through them here. Calling web_sys::Event::target on an event returns an optional web_sys::EventTarget type, which might not seem very useful when you want to know the value of your input element.

In all the approaches below we are going to tackle the same problem, so it's clear where the approach differs opposed to the problem at hand.

The Problem:

We have an oninput Callback on my <input> element and each time it is invoked we want to send an update Msg to our component. We really want to get the value as a number or f64 for rust.

Our Msg enum looks like this:

pub enum Msg {
InputValue(f64),
}

Using JsCast

The wasm-bindgen crate has a useful trait; JsCast which allows us to hop and skip our way to the type we want, as long as it implements JsCast. We can do this cautiously, which involves some runtime checks and failure types like Option and Result, or we can do it dangerously.

Enough talk, more code:

Cargo.toml
[dependencies]
# need wasm-bindgen for JsCast
wasm-bindgen = "0.2"
use wasm_bindgen::JsCast;
use yew::{
html,
web_sys::{EventTarget, HtmlInputElement},
Component, ComponentLink, Html, InputData,
};

pub struct Comp {
link: ComponentLink<Self>,
}

pub enum Msg {
InputValue(f64),
}

impl Component for Comp {
type Message = Msg;
type Properties = ();

fn create(_: Self::Properties, link: ComponentLink<Self>) -> Self {
Self { link }
}

fn update(&mut self, msg: Self::Message) -> yew::ShouldRender {
let Msg::InputValue(value) = msg;
// do something with value
todo!()
}

fn change(&mut self, _props: Self::Properties) -> yew::ShouldRender {
false
}

fn view(&self) -> Html {
let link = &self.link;

// Use batch_callback so if something unexpected happens we can return
// None and do nothing
let on_cautious_change = link.batch_callback(|e: InputData| {
let e = e.event;
// When events are created the target is undefined, it's only
// when dispatched does the target get added.
let target: Option<EventTarget> = e.target();
// Events can bubble so this listener might catch events from child
// elements which are not of type HtmlInputElement
let input = target.and_then(|t| t.dyn_into::<HtmlInputElement>().ok());

input.map(|input| Msg::InputValue(input.value_as_number()))
});

let on_dangerous_change = link.callback(|e: InputData| {
let e = e.event;
let target: EventTarget = e
.target()
.expect("Event should have a target when dispatched");
// You must KNOW target is a HtmlInputElement, otherwise
// the call to value would be Undefined Behaviour (UB).
Msg::InputValue(
target
.unchecked_into::<HtmlInputElement>()
.value_as_number(),
)
});

html! {
<>
<label for="cautious-input">
{ "My cautious input:" }
<input oninput={on_cautious_change}
id="cautious-input"
type="text"
/>
</label>
<label for="dangerous-input">
{ "My dangerous input:" }
<input oninput={on_dangerous_change}
id="dangerous-input"
type="text"
/>
</label>
</>
}
}
}
tip

Use batch_callback when you want to conditionally return a value from a Callback.

The methods from JsCast are dyn_into and unchecked_into and you can probably see, they allowed us to go from EventTarget to HtmlInputElement. The dyn_into method is cautious because at runtime it will check whether the type is actually a HtmlInputElement and if not return an Err(JsValue), the JsValue is a catch-all type and is essentially giving you back the object to try again.

At this point you might be thinking... when is the dangerous version ok to use? In the case above it is safe1 as we've set the Callback on to an element with no children so the target can only be that same element.

1 As safe as anything can be when JS land is involved.

Using NodeRef

NodeRef can be used instead of querying the event given to a Callback.

use yew::{html, web_sys::HtmlInputElement, Component, ComponentLink, Html, NodeRef};

pub struct Comp {
link: ComponentLink<Self>,
my_input: NodeRef,
}

pub enum Msg {
InputValue(f64),
}

impl Component for Comp {
type Message = Msg;
type Properties = ();

fn create(_: Self::Properties, link: ComponentLink<Self>) -> Self {
Self {
link,
my_input: NodeRef::default(),
}
}

fn update(&mut self, msg: Self::Message) -> yew::ShouldRender {
let Msg::InputValue(value) = msg;
// do something with value
todo!()
}

fn change(&mut self, _props: Self::Properties) -> yew::ShouldRender {
false
}

fn view(&self) -> Html {
let my_input_ref = self.my_input.clone();

let oninput = self.link.batch_callback(move |_| {
let input = my_input_ref.cast::<HtmlInputElement>();

input.map(|input| Msg::InputValue(input.value_as_number()))
});

html! {
<>
<label for="my-input">
{ "My input:" }
<input ref={self.my_input.clone()}
oninput={oninput}
id="my-input"
type="text"
/>
</label>
</>
}
}
}

Using NodeRef, you can ignore the event and use the NodeRef::cast method to get an Option<HtmlInputElement> - this is optional as calling cast before the NodeRef has been set, or when the type doesn't match will return None.

You might also see by using NodeRef we don't have to send the f64 back in the Msg::InputValue as we always have my_input in the component state - so we could do the following:

use yew::{html, web_sys::HtmlInputElement, Component, ComponentLink, Html, NodeRef};

pub struct Comp {
link: ComponentLink<Self>,
my_input: NodeRef,
}

pub enum Msg {
// Signal the input element has changed
InputChanged,
}

impl Component for Comp {
type Message = Msg;
type Properties = ();

fn create(_: Self::Properties, link: ComponentLink<Self>) -> Self {
Self {
link,
my_input: NodeRef::default(),
}
}

fn change(&mut self, _props: Self::Properties) -> yew::ShouldRender {
false
}

fn update(&mut self, msg: Self::Message) -> bool {
match msg {
Msg::InputChanged => {
if let Some(input) = self.my_input.cast::<HtmlInputElement>() {
let value = input.value_as_number();
// do something with value

true
} else {
false
}
}
}
}

fn view(&self) -> Html {
let oninput = self.link.callback(|_| Msg::InputChanged);

html! {
<label for="my-input">
{ "My input:" }
<input ref={self.my_input.clone()}
oninput={oninput}
id="my-input"
type="text"
/>
</label>
}
}
}

Which approach you take depends on your component and your preferences, there is no blessed way per se.

Manual event listener

You may want to listen to an event that is not supported by Yew's html macro, see the supported events listed here.

In order to add an event listener to one of elements manually we need the help of NodeRef so that in the rendered method we can add a listener using the web-sys and wasm-bindgen API. We do this in rendered as this is the only time we can guarantee that the element exists in the browser, Yew needs some time to create them after view is called.

The examples below are going to show adding listeners for the made-up custard event. All events either unsupported by yew or custom can be represented as a web_sys::Event. If you need to access a specific method or field on a custom / unsupported event then you can use the methods of JsCast in order to convert to the type required.

Using Closure (verbose)

Using the web-sys and wasm-bindgen API's directly for this can be a bit painful.. so brace yourself (there is a more concise way thanks to gloo).

use wasm_bindgen::{prelude::Closure, JsCast};
use yew::{
html,
web_sys::{Event, HtmlElement},
Component, ComponentLink, Html, NodeRef,
};

pub struct Comp {
link: ComponentLink<Self>,
my_div: NodeRef,
custard_listener: Option<Closure<dyn Fn(Event)>>,
}

pub enum Msg {
Custard,
}

impl Component for Comp {
type Message = Msg;
type Properties = ();

fn create(_: Self::Properties, link: ComponentLink<Self>) -> Self {
Self {
link,
my_div: NodeRef::default(),
custard_listener: None,
}
}

fn update(&mut self, msg: Self::Message) -> bool {
match msg {
Msg::Custard => {
// do something about custard..
true
}
}
}

fn change(&mut self, _props: Self::Properties) -> yew::ShouldRender {
false
}

fn view(&self) -> Html {
html! {
<div ref={self.my_div.clone()} id="my-div"></div>
}
}

fn rendered(&mut self, first_render: bool) {
if !first_render {
return;
}

if let Some(element) = self.my_div.cast::<HtmlElement>() {
// Create your Callback as you normally would
let oncustard = self.link.callback(|_: Event| Msg::Custard);

// Create a Closure from a Box<dyn Fn> - this has to be 'static
let listener =
Closure::<dyn Fn(Event)>::wrap(
Box::new(move |e: Event| oncustard.emit(e))
);
element
.add_event_listener_with_callback(
"custard",
listener.as_ref().unchecked_ref()
)
.unwrap();

// Need to save listener in the component otherwise when the
// event is fired it will try and call the listener that no longer
// exists in memory!
self.custard_listener = Some(listener);
}
}

fn destroy(&mut self) {
// All done with the component but need to remove
// the event listener before the custard_listener memory
// goes out of scope.
if let (Some(element), Some(listener)) = (
self.my_div.cast::<HtmlElement>(),
self.custard_listener.take(),
) {
element
.remove_event_listener_with_callback(
"custard",
listener.as_ref().unchecked_ref()
)
.unwrap();
}
}
}

For more information on Closures, see The wasm-bindgen Guide.

Using gloo (concise)

The easier way is with gloo, more specifically gloo_events which is an abstraction for web-sys, wasm-bindgen.

gloo_events has the EventListener type which can be used to create and store the event listener.

Cargo.toml
[dependencies]
gloo-events = "0.1"
use yew::{
html,
web_sys::{Event, HtmlElement},
Component, ComponentLink, Html, NodeRef,
};

use gloo::events::EventListener;

pub struct Comp {
link: ComponentLink<Self>,
my_div: NodeRef,
custard_listener: Option<EventListener>,
}

pub enum Msg {
Custard,
}

impl Component for Comp {
type Message = Msg;
type Properties = ();

fn create(_: Self::Properties, link: ComponentLink<Self>) -> Self {
Self {
link,
my_div: NodeRef::default(),
custard_listener: None,
}
}

fn update(&mut self, msg: Self::Message) -> bool {
match msg {
Msg::Custard => {
// do something about custard..
true
}
}
}

fn change(&mut self, _props: Self::Properties) -> yew::ShouldRender {
false
}

fn view(&self) -> Html {
html! {
<div ref={self.my_div.clone()} id="my-div"></div>
}
}

fn rendered(&mut self, first_render: bool) {
if !first_render {
return;
}

if let Some(element) = self.my_div.cast::<HtmlElement>() {
// Create your Callback as you normally would
let oncustard = self.link.callback(|_: Event| Msg::Custard);

let listener =
EventListener::new(
&element,
"custard",
move |e| oncustard.emit(e.clone())
);

self.custard_listener = Some(listener);
}
}
}

Notice that when using an EventListener you don't need to do anything when the component is about to be destroyed as the EventListener has a drop implementation which will remove the event listener from the element.

For more information on EventListener, see the gloo_events docs.rs.