Cross-platform native GUI library for 🦀 Rust
See the codeWebsite | Documentation | Discord | Contact
Freya is a cross-platform, native, declarative GUI library for Rust 🦀.
Latest stable release:
freya = "0.4"
Next release:
freya = "0.5.0-rc.9"
main branch:
freya = { git = "https://github.com/marc2332/freya", branch = "main" }
Make sure to have Development Setup ready.
⚠️ This repo uses git submodules, cloning without
--recurse-submoduleswill make the examples fail to compile.
git clone --recurse-submodules https://github.com/marc2332/freya.git
cd freya
Already cloned without submodules? Run
git submodule update --init --recursiveto fetch them.
Then run an example:
cargo run --example counter
⚠️ If you happen to be on Windows using
windows-gnuand get compile errors, maybe go check this issue.
Freya’s component model lets you create reusable UI elements that automatically re-render when the state they depend on changes. Components can hold their own internal state or subscribe to shared state, and they produce UI as their output. Any type that implements the Component trait can be a component, while the root (app) component can simply be a function. Built-in examples include components like Button and Switch.
|
|
Freya comes with a set of components out of the box, from simple like Button, Switch, Slider to more complex like VirtualScrollView, Calendar, ColorPicker, etc.
You can check all the examples that start with component_ in the examples folder.
Example of component_input.rs:
Create transitions for colors, sizes, positions, and other visual properties. The animation API gives you full control over timing, easing functions, and animation sequences.
use freya::prelude::*;
fn app() -> impl IntoElement {
let mut animation = use_animation(|_| AnimColor::new((246, 240, 240), (205, 86, 86)).time(400));
rect()
.background(&*animation.read())
.expanded()
.center()
.spacing(8.0)
.child(
Button::new()
.on_press(move |_| {
animation.start();
})
.child("Start"),
)
.child(
Button::new()
.on_press(move |_| {
animation.reverse();
})
.child("Reverse"),
)
}
Freya provides text editing capabilities that go beyond simple input fields. You can create rich text editors with cursor management, text selection, keyboard shortcuts, custom formatting, virtulization and more.
use freya::prelude::*;
fn app() -> impl IntoElement {
let holder = use_state(ParagraphHolder::default);
let mut editable = use_editable(|| "Hello, World!".to_string(), EditableConfig::new);
let focus = use_focus();
paragraph()
.a11y_id(focus.a11y_id())
.cursor_index(editable.editor().read().cursor_pos())
.highlights(
editable
.editor()
.read()
.get_selection()
.map(|selection| vec![selection])
.unwrap_or_default(),
)
.on_mouse_down(move |e: Event<MouseEventData>| {
focus.request_focus();
editable.process_event(EditableEvent::Down {
location: e.element_location,
editor_line: EditorLine::SingleParagraph,
holder: &holder.read(),
});
})
.on_mouse_move(move |e: Event<MouseEventData>| {
editable.process_event(EditableEvent::Move {
location: e.element_location,
editor_line: EditorLine::SingleParagraph,
holder: &holder.read(),
});
})
.on_global_pointer_up(move |_| editable.process_event(EditableEvent::Release))
.on_key_down(move |e: Event<KeyboardEventData>| {
editable.process_event(EditableEvent::KeyDown {
key: &e.key,
modifiers: e.modifiers,
});
})
.on_key_up(move |e: Event<KeyboardEventData>| {
editable.process_event(EditableEvent::KeyUp { key: &e.key });
})
.span(editable.editor().read().to_string())
.holder(holder.read().clone())
}
Create and control text Code Editors. It is state agnostic so as long as it can be turned into a Writable it will work. Uses Rope for text editing and tree-sitter for syntax highlighting. You bring your own tree-sitter grammar and its highlights query, so any language can be supported.
Enable with the code-editor feature.
fn app() -> impl IntoElement {
use_init_theme(dark_theme);
let focus = use_focus();
let editor = use_state(|| {
let path = PathBuf::from("./crates/freya-code-editor/src/editor_ui.rs");
let rope = Rope::from_str(&std::fs::read_to_string(&path).unwrap());
let language = EditorLanguage::new(
tree_sitter_rust::LANGUAGE,
tree_sitter_rust::HIGHLIGHTS_QUERY,
);
let mut editor = CodeEditorData::new(rope, language);
editor.parse();
editor.measure(14., "Jetbrains Mono");
editor
});
CodeEditor::new(editor, focus.a11y_id())
}
Render Markdown documents with the MarkdownViewer component.
Enable with the markdown feature.
fn app() -> impl IntoElement {
MarkdownViewer::new("# Hello World\n\nThis is **bold** and *italic* text.")
}
Define routes, manage navigation state, and transition between different views.
Enable with the router feature.
use freya::prelude::*;
use freya::router::prelude::*;
#[derive(Routable, Clone, PartialEq)]
#[rustfmt::skip]
pub enum Route {
#[layout(AppBottomBar)]
#[route("/")]
Home,
#[route("/settings")]
Settings,
}
fn app() -> impl IntoElement {
router::<Route>(|| RouterConfig::default().with_initial_path(Route::Settings))
}
#[derive(PartialEq)]
struct AppBottomBar;
impl Component for AppBottomBar {
fn render(&self) -> impl IntoElement {
rect()
.native_router()
.content(Content::flex())
.child(
rect()
.width(Size::fill())
.height(Size::flex(1.))
.center()
.child(outlet::<Route>()),
)
.child(
rect()
.horizontal()
.width(Size::fill())
.main_align(Alignment::center())
.padding(8.)
.spacing(8.)
.child(
Link::new(Route::Home)
.child(FloatingTab::new().child("Home"))
.activable_route(Route::Home)
.exact(true),
)
.child(
Link::new(Route::Settings)
.child(FloatingTab::new().child("Settings"))
.activable_route(Route::Settings)
.exact(true),
),
)
}
}
#[derive(PartialEq)]
struct Home {}
impl Component for Home {
fn render(&self) -> impl IntoElement {
Button::new()
.on_press(|_| {
RouterContext::get().replace(Route::Settings);
})
.child("Go Settings")
}
}
#[derive(PartialEq)]
struct Settings {}
impl Component for Settings {
fn render(&self) -> impl IntoElement {
Button::new()
.on_press(|_| {
let _ = RouterContext::get().replace(Route::Home);
})
.child("Go Home")
}
}
Freya's freya-radio state management system provides efficient global state management through a channels system. Components subscribe to specific "channels" and only receive updates when data is mutated and notified through their channel.
Enable with the radio feature.
use freya::prelude::*;
use freya::radio::*;
#[derive(Default)]
struct Data {
pub lists: Vec<Vec<String>>,
}
#[derive(PartialEq, Eq, Clone, Debug, Copy, Hash)]
pub enum DataChannel {
ListCreation,
SpecificListItemUpdate(usize),
}
impl RadioChannel<Data> for DataChannel {}
fn app() -> impl IntoElement {
use_init_radio_station::<Data, DataChannel>(Data::default);
let mut radio = use_radio::<Data, DataChannel>(DataChannel::ListCreation);
let on_press = move |_| {
radio.write().lists.push(Vec::default());
};
rect()
.horizontal()
.child(Button::new().on_press(on_press).child("Add new list"))
.children(
radio
.read()
.lists
.iter()
.enumerate()
.map(|(list_n, _)| ListComp(list_n).into()),
)
}
#[derive(PartialEq)]
struct ListComp(usize);
impl Component for ListComp {
fn render(&self) -> impl IntoElement {
let list_n = self.0;
let mut radio = use_radio::<Data, DataChannel>(DataChannel::SpecificListItemUpdate(list_n));
println!("Running DataChannel::SpecificListItemUpdate({list_n})");
rect()
.child(
Button::new()
.on_press(move |_| radio.write().lists[list_n].push("Hello, World".to_string()))
.child("New Item"),
)
.children(
radio.read().lists[list_n]
.iter()
.enumerate()
.map(move |(i, item)| label().key(i).text(item.clone()).into()),
)
}
}
Easily integrate icons into your applications, only supports Lucide at the moment.
use freya::prelude::*;
use freya::icons;
fn app() -> impl IntoElement {
SvgViewer::new(icons::lucide::antenna())
.color((120, 50, 255))
.expanded()
}
Using freya-testing you can test your Freya components in a no-window (headless) environment. You can decide to render the app at any moment to a file though. freya-testing is actually used by Freya itself to test all the out of the box components and other APIs.
use freya::prelude::*;
use freya_testing::prelude::*;
fn app() -> impl IntoElement {
let mut state = use_consume::<State<i32>>();
rect()
.expanded()
.center()
.background((240, 240, 240))
.on_press(move |_| *state.write() += 1)
.child(format!("Clicked: {}", state.read()))
}
fn main() {
// Create headless testing runner
let (mut test, state) = TestingRunner::new(
app,
(300., 300.).into(),
|runner| runner.provide_root_context(|| State::create(0)),
1.,
);
test.sync_and_update();
assert_eq!(*state.peek(), 0);
// Simulate user interactions
test.click_cursor((15., 15.));
assert_eq!(*state.peek(), 1);
// Render the current ui state to a file
test.render_to_file("./demo-1.png");
}
Using the Plotters library, you can create charts, graphs, and data visualizations directly within your application.
Enable with the ploters feature.
use freya::prelude::*;
use freya::plot::*;
use freya::plot::plotters::*;
fn on_render(ctx: &mut FillRenderContext, (cursor_x, cursor_y): (f64, f64)) {
let backend = PlotSkiaBackend::new(
ctx.canvas,
ctx.font_collection,
ctx.layout_node.area.size.to_i32().to_tuple(),
).into_drawing_area();
backend.fill(&WHITE).unwrap();
let pitch = std::f64::consts::PI * (0.5 - cursor_y / ctx.layout_node.area.height() as f64);
let yaw = std::f64::consts::PI * 2.0 * (cursor_x / ctx.layout_node.area.width() as f64 - 0.5);
let scale = 0.4 + 0.6 * (1.0 - cursor_y / ctx.layout_node.area.height() as f64);
let x_axis = (-3.0..3.0).step(0.1);
let z_axis = (-3.0..3.0).step(0.1);
let mut chart = ChartBuilder::on(&backend)
.caption("3D Surface Plot", ("sans", 20))
.build_cartesian_3d(x_axis.clone(), -3.0..3.0, z_axis.clone())
.unwrap();
chart.with_projection(|mut pb| {
pb.pitch = pitch;
pb.yaw = yaw;
pb.scale = scale;
pb.into_matrix()
});
chart
.draw_series(
SurfaceSeries::xoz(
(-30..30).map(|f| f as f64 / 10.0),
(-30..30).map(|f| f as f64 / 10.0),
|x, z| (x * x + z * z).cos(),
)
.style(BLUE.mix(0.2).filled()),
)
.unwrap()
.label("Interactive Surface")
.legend(|(x, y)| Rectangle::new([(x + 5, y - 5), (x + 15, y + 5)], BLUE.mix(0.5).filled()));
}
fn app() -> impl IntoElement {
let mut cursor_position = use_state(CursorPoint::default);
let on_global_pointer_move = move |e: Event<PointerEventData>| {
// Dont move when the cursor goes outside the window
if e.global_location().to_tuple() != (-1., -1.) {
cursor_position.set(e.global_location());
let platform = Platform::get();
platform.send(UserEvent::RequestRedraw);
}
};
rect().background(RenderCallback::new(move |context| {
on_render(context, cursor_position().to_tuple());
}))
.expanded()
.on_global_pointer_move(on_global_pointer_move)
}
Freya supports internationalization with built-in support for the Fluent localization system. Easily manage translations, pluralization, and locale-specific formatting.
Enable with the i18n feature.
use freya::prelude::*;
use freya::i18n::*;
fn app() -> impl IntoElement {
let mut i18n = use_init_i18n(|| {
I18nConfig::new(langid!("en-US"))
.with_locale((langid!("en-US"), include_str!("./i18n/en-US.ftl")))
.with_locale((langid!("es-ES"), PathBuf::from("./examples/i18n/es-ES.ftl")))
});
let change_to_english = move |_| i18n.set_language(langid!("en-US"));
let change_to_spanish = move |_| i18n.set_language(langid!("es-ES"));
rect()
.expanded()
.center()
.child(
rect()
.horizontal()
.child(Button::new().on_press(change_to_english).child("English"))
.child(Button::new().on_press(change_to_spanish).child("Spanish")),
)
.child(t!("hello_world"))
.child(t!("hello", name: "Freya!"))
}
Freya provides Material Design-inspired style modifiers.
Enable with the material-design feature.
use freya::prelude::*;
use freya::material_design::*;
fn app() -> impl IntoElement {
rect().center().expanded().child(
Button::new()
.on_press(|_| println!("Material button pressed"))
.ripple() // Adds Material Design ripple effect
.child("Material Button"),
)
}
Render HTML and CSS content natively inside your applications, powered by the Blitz engine.
Enable with the html feature.
use freya::prelude::*;
use freya::html::*;
fn app() -> impl IntoElement {
let handle = use_html(|| HtmlSource::url("https://example.com"));
rect()
.expanded()
.center()
.child(HtmlView::new(handle).expanded())
}
Freya includes terminal emulation capabilities with full PTY (pseudo-terminal) support. Create integrated terminal applications, SSH clients, or development tools.
Enable with the terminal-pty feature.
use freya::prelude::*;
use freya::terminal::*;
fn app() -> impl IntoElement {
let mut handle = use_state(|| {
let mut cmd = CommandBuilder::new("bash");
cmd.env("TERM", "xterm-256color");
cmd.env("COLORTERM", "truecolor");
TerminalHandle::new(TerminalId::new(), PtyBackend::new(cmd), None).ok()
});
rect()
.expanded()
.center()
.background((30, 30, 30))
.color((245, 245, 245))
.padding(6.)
.child(if let Some(handle) = handle.read().clone() {
Terminal::new(handle.clone())
.a11y_id(focus.a11y_id())
.on_key_down(move |e: Event<KeyboardEventData>| {
let _ = handle.write_key(&e.key, e.modifiers);
})
} else {
"Terminal exited".into_element()
})
}
Examine the component tree in real-time.
Enable the devtools feature in freya and then run the devtools app.
If you are interested in contributing please make sure to have read the Contributing guide first!
You may contact me for questions, collaboration or anything that comes to your mind at marc@mespin.me.
If you are interested in supporting the development of this project feel free to donate to my Github Sponsor page.
Thanks to my sponsors for supporting this project! 😄




calc() function and also pushed for improvements in the animation APIs.Freya 0.1, 0.2 and 0.3 were based on the core crates of Dioxus. Starting 0.4 Freya does no longer use Dioxus, instead it uses its own reactive core, partially inspired by Dioxus but yet with lots of differences.
A Claude Code skill with Freya best practices and patterns is available. Install it with:
/plugin marketplace add marc2332/freya
/plugin install freya@freya-marketplace
For other AI coding agents, you can use the skill document directly as context: plugins/freya/skills/freya/SKILL.md
(top 24 of 79)
5,482 followers · starred Aug 2026
1,061 followers · starred Apr 2025
326 followers · starred Jun 2025
5,444 followers · starred Jan 2025
Cross-platform native GUI library for 🦀 Rust
See the codeWebsite | Documentation | Discord | Contact
Freya is a cross-platform, native, declarative GUI library for Rust 🦀.
Latest stable release:
freya = "0.4"
Next release:
freya = "0.5.0-rc.9"
main branch:
freya = { git = "https://github.com/marc2332/freya", branch = "main" }
Make sure to have Development Setup ready.
⚠️ This repo uses git submodules, cloning without
--recurse-submoduleswill make the examples fail to compile.
git clone --recurse-submodules https://github.com/marc2332/freya.git
cd freya
Already cloned without submodules? Run
git submodule update --init --recursiveto fetch them.
Then run an example:
cargo run --example counter
⚠️ If you happen to be on Windows using
windows-gnuand get compile errors, maybe go check this issue.
Freya’s component model lets you create reusable UI elements that automatically re-render when the state they depend on changes. Components can hold their own internal state or subscribe to shared state, and they produce UI as their output. Any type that implements the Component trait can be a component, while the root (app) component can simply be a function. Built-in examples include components like Button and Switch.
|
|
Freya comes with a set of components out of the box, from simple like Button, Switch, Slider to more complex like VirtualScrollView, Calendar, ColorPicker, etc.
You can check all the examples that start with component_ in the examples folder.
Example of component_input.rs:
Create transitions for colors, sizes, positions, and other visual properties. The animation API gives you full control over timing, easing functions, and animation sequences.
use freya::prelude::*;
fn app() -> impl IntoElement {
let mut animation = use_animation(|_| AnimColor::new((246, 240, 240), (205, 86, 86)).time(400));
rect()
.background(&*animation.read())
.expanded()
.center()
.spacing(8.0)
.child(
Button::new()
.on_press(move |_| {
animation.start();
})
.child("Start"),
)
.child(
Button::new()
.on_press(move |_| {
animation.reverse();
})
.child("Reverse"),
)
}
Freya provides text editing capabilities that go beyond simple input fields. You can create rich text editors with cursor management, text selection, keyboard shortcuts, custom formatting, virtulization and more.
use freya::prelude::*;
fn app() -> impl IntoElement {
let holder = use_state(ParagraphHolder::default);
let mut editable = use_editable(|| "Hello, World!".to_string(), EditableConfig::new);
let focus = use_focus();
paragraph()
.a11y_id(focus.a11y_id())
.cursor_index(editable.editor().read().cursor_pos())
.highlights(
editable
.editor()
.read()
.get_selection()
.map(|selection| vec![selection])
.unwrap_or_default(),
)
.on_mouse_down(move |e: Event<MouseEventData>| {
focus.request_focus();
editable.process_event(EditableEvent::Down {
location: e.element_location,
editor_line: EditorLine::SingleParagraph,
holder: &holder.read(),
});
})
.on_mouse_move(move |e: Event<MouseEventData>| {
editable.process_event(EditableEvent::Move {
location: e.element_location,
editor_line: EditorLine::SingleParagraph,
holder: &holder.read(),
});
})
.on_global_pointer_up(move |_| editable.process_event(EditableEvent::Release))
.on_key_down(move |e: Event<KeyboardEventData>| {
editable.process_event(EditableEvent::KeyDown {
key: &e.key,
modifiers: e.modifiers,
});
})
.on_key_up(move |e: Event<KeyboardEventData>| {
editable.process_event(EditableEvent::KeyUp { key: &e.key });
})
.span(editable.editor().read().to_string())
.holder(holder.read().clone())
}
Create and control text Code Editors. It is state agnostic so as long as it can be turned into a Writable it will work. Uses Rope for text editing and tree-sitter for syntax highlighting. You bring your own tree-sitter grammar and its highlights query, so any language can be supported.
Enable with the code-editor feature.
fn app() -> impl IntoElement {
use_init_theme(dark_theme);
let focus = use_focus();
let editor = use_state(|| {
let path = PathBuf::from("./crates/freya-code-editor/src/editor_ui.rs");
let rope = Rope::from_str(&std::fs::read_to_string(&path).unwrap());
let language = EditorLanguage::new(
tree_sitter_rust::LANGUAGE,
tree_sitter_rust::HIGHLIGHTS_QUERY,
);
let mut editor = CodeEditorData::new(rope, language);
editor.parse();
editor.measure(14., "Jetbrains Mono");
editor
});
CodeEditor::new(editor, focus.a11y_id())
}
Render Markdown documents with the MarkdownViewer component.
Enable with the markdown feature.
fn app() -> impl IntoElement {
MarkdownViewer::new("# Hello World\n\nThis is **bold** and *italic* text.")
}
Define routes, manage navigation state, and transition between different views.
Enable with the router feature.
use freya::prelude::*;
use freya::router::prelude::*;
#[derive(Routable, Clone, PartialEq)]
#[rustfmt::skip]
pub enum Route {
#[layout(AppBottomBar)]
#[route("/")]
Home,
#[route("/settings")]
Settings,
}
fn app() -> impl IntoElement {
router::<Route>(|| RouterConfig::default().with_initial_path(Route::Settings))
}
#[derive(PartialEq)]
struct AppBottomBar;
impl Component for AppBottomBar {
fn render(&self) -> impl IntoElement {
rect()
.native_router()
.content(Content::flex())
.child(
rect()
.width(Size::fill())
.height(Size::flex(1.))
.center()
.child(outlet::<Route>()),
)
.child(
rect()
.horizontal()
.width(Size::fill())
.main_align(Alignment::center())
.padding(8.)
.spacing(8.)
.child(
Link::new(Route::Home)
.child(FloatingTab::new().child("Home"))
.activable_route(Route::Home)
.exact(true),
)
.child(
Link::new(Route::Settings)
.child(FloatingTab::new().child("Settings"))
.activable_route(Route::Settings)
.exact(true),
),
)
}
}
#[derive(PartialEq)]
struct Home {}
impl Component for Home {
fn render(&self) -> impl IntoElement {
Button::new()
.on_press(|_| {
RouterContext::get().replace(Route::Settings);
})
.child("Go Settings")
}
}
#[derive(PartialEq)]
struct Settings {}
impl Component for Settings {
fn render(&self) -> impl IntoElement {
Button::new()
.on_press(|_| {
let _ = RouterContext::get().replace(Route::Home);
})
.child("Go Home")
}
}
Freya's freya-radio state management system provides efficient global state management through a channels system. Components subscribe to specific "channels" and only receive updates when data is mutated and notified through their channel.
Enable with the radio feature.
use freya::prelude::*;
use freya::radio::*;
#[derive(Default)]
struct Data {
pub lists: Vec<Vec<String>>,
}
#[derive(PartialEq, Eq, Clone, Debug, Copy, Hash)]
pub enum DataChannel {
ListCreation,
SpecificListItemUpdate(usize),
}
impl RadioChannel<Data> for DataChannel {}
fn app() -> impl IntoElement {
use_init_radio_station::<Data, DataChannel>(Data::default);
let mut radio = use_radio::<Data, DataChannel>(DataChannel::ListCreation);
let on_press = move |_| {
radio.write().lists.push(Vec::default());
};
rect()
.horizontal()
.child(Button::new().on_press(on_press).child("Add new list"))
.children(
radio
.read()
.lists
.iter()
.enumerate()
.map(|(list_n, _)| ListComp(list_n).into()),
)
}
#[derive(PartialEq)]
struct ListComp(usize);
impl Component for ListComp {
fn render(&self) -> impl IntoElement {
let list_n = self.0;
let mut radio = use_radio::<Data, DataChannel>(DataChannel::SpecificListItemUpdate(list_n));
println!("Running DataChannel::SpecificListItemUpdate({list_n})");
rect()
.child(
Button::new()
.on_press(move |_| radio.write().lists[list_n].push("Hello, World".to_string()))
.child("New Item"),
)
.children(
radio.read().lists[list_n]
.iter()
.enumerate()
.map(move |(i, item)| label().key(i).text(item.clone()).into()),
)
}
}
Easily integrate icons into your applications, only supports Lucide at the moment.
use freya::prelude::*;
use freya::icons;
fn app() -> impl IntoElement {
SvgViewer::new(icons::lucide::antenna())
.color((120, 50, 255))
.expanded()
}
Using freya-testing you can test your Freya components in a no-window (headless) environment. You can decide to render the app at any moment to a file though. freya-testing is actually used by Freya itself to test all the out of the box components and other APIs.
use freya::prelude::*;
use freya_testing::prelude::*;
fn app() -> impl IntoElement {
let mut state = use_consume::<State<i32>>();
rect()
.expanded()
.center()
.background((240, 240, 240))
.on_press(move |_| *state.write() += 1)
.child(format!("Clicked: {}", state.read()))
}
fn main() {
// Create headless testing runner
let (mut test, state) = TestingRunner::new(
app,
(300., 300.).into(),
|runner| runner.provide_root_context(|| State::create(0)),
1.,
);
test.sync_and_update();
assert_eq!(*state.peek(), 0);
// Simulate user interactions
test.click_cursor((15., 15.));
assert_eq!(*state.peek(), 1);
// Render the current ui state to a file
test.render_to_file("./demo-1.png");
}
Using the Plotters library, you can create charts, graphs, and data visualizations directly within your application.
Enable with the ploters feature.
use freya::prelude::*;
use freya::plot::*;
use freya::plot::plotters::*;
fn on_render(ctx: &mut FillRenderContext, (cursor_x, cursor_y): (f64, f64)) {
let backend = PlotSkiaBackend::new(
ctx.canvas,
ctx.font_collection,
ctx.layout_node.area.size.to_i32().to_tuple(),
).into_drawing_area();
backend.fill(&WHITE).unwrap();
let pitch = std::f64::consts::PI * (0.5 - cursor_y / ctx.layout_node.area.height() as f64);
let yaw = std::f64::consts::PI * 2.0 * (cursor_x / ctx.layout_node.area.width() as f64 - 0.5);
let scale = 0.4 + 0.6 * (1.0 - cursor_y / ctx.layout_node.area.height() as f64);
let x_axis = (-3.0..3.0).step(0.1);
let z_axis = (-3.0..3.0).step(0.1);
let mut chart = ChartBuilder::on(&backend)
.caption("3D Surface Plot", ("sans", 20))
.build_cartesian_3d(x_axis.clone(), -3.0..3.0, z_axis.clone())
.unwrap();
chart.with_projection(|mut pb| {
pb.pitch = pitch;
pb.yaw = yaw;
pb.scale = scale;
pb.into_matrix()
});
chart
.draw_series(
SurfaceSeries::xoz(
(-30..30).map(|f| f as f64 / 10.0),
(-30..30).map(|f| f as f64 / 10.0),
|x, z| (x * x + z * z).cos(),
)
.style(BLUE.mix(0.2).filled()),
)
.unwrap()
.label("Interactive Surface")
.legend(|(x, y)| Rectangle::new([(x + 5, y - 5), (x + 15, y + 5)], BLUE.mix(0.5).filled()));
}
fn app() -> impl IntoElement {
let mut cursor_position = use_state(CursorPoint::default);
let on_global_pointer_move = move |e: Event<PointerEventData>| {
// Dont move when the cursor goes outside the window
if e.global_location().to_tuple() != (-1., -1.) {
cursor_position.set(e.global_location());
let platform = Platform::get();
platform.send(UserEvent::RequestRedraw);
}
};
rect().background(RenderCallback::new(move |context| {
on_render(context, cursor_position().to_tuple());
}))
.expanded()
.on_global_pointer_move(on_global_pointer_move)
}
Freya supports internationalization with built-in support for the Fluent localization system. Easily manage translations, pluralization, and locale-specific formatting.
Enable with the i18n feature.
use freya::prelude::*;
use freya::i18n::*;
fn app() -> impl IntoElement {
let mut i18n = use_init_i18n(|| {
I18nConfig::new(langid!("en-US"))
.with_locale((langid!("en-US"), include_str!("./i18n/en-US.ftl")))
.with_locale((langid!("es-ES"), PathBuf::from("./examples/i18n/es-ES.ftl")))
});
let change_to_english = move |_| i18n.set_language(langid!("en-US"));
let change_to_spanish = move |_| i18n.set_language(langid!("es-ES"));
rect()
.expanded()
.center()
.child(
rect()
.horizontal()
.child(Button::new().on_press(change_to_english).child("English"))
.child(Button::new().on_press(change_to_spanish).child("Spanish")),
)
.child(t!("hello_world"))
.child(t!("hello", name: "Freya!"))
}
Freya provides Material Design-inspired style modifiers.
Enable with the material-design feature.
use freya::prelude::*;
use freya::material_design::*;
fn app() -> impl IntoElement {
rect().center().expanded().child(
Button::new()
.on_press(|_| println!("Material button pressed"))
.ripple() // Adds Material Design ripple effect
.child("Material Button"),
)
}
Render HTML and CSS content natively inside your applications, powered by the Blitz engine.
Enable with the html feature.
use freya::prelude::*;
use freya::html::*;
fn app() -> impl IntoElement {
let handle = use_html(|| HtmlSource::url("https://example.com"));
rect()
.expanded()
.center()
.child(HtmlView::new(handle).expanded())
}
Freya includes terminal emulation capabilities with full PTY (pseudo-terminal) support. Create integrated terminal applications, SSH clients, or development tools.
Enable with the terminal-pty feature.
use freya::prelude::*;
use freya::terminal::*;
fn app() -> impl IntoElement {
let mut handle = use_state(|| {
let mut cmd = CommandBuilder::new("bash");
cmd.env("TERM", "xterm-256color");
cmd.env("COLORTERM", "truecolor");
TerminalHandle::new(TerminalId::new(), PtyBackend::new(cmd), None).ok()
});
rect()
.expanded()
.center()
.background((30, 30, 30))
.color((245, 245, 245))
.padding(6.)
.child(if let Some(handle) = handle.read().clone() {
Terminal::new(handle.clone())
.a11y_id(focus.a11y_id())
.on_key_down(move |e: Event<KeyboardEventData>| {
let _ = handle.write_key(&e.key, e.modifiers);
})
} else {
"Terminal exited".into_element()
})
}
Examine the component tree in real-time.
Enable the devtools feature in freya and then run the devtools app.
If you are interested in contributing please make sure to have read the Contributing guide first!
You may contact me for questions, collaboration or anything that comes to your mind at marc@mespin.me.
If you are interested in supporting the development of this project feel free to donate to my Github Sponsor page.
Thanks to my sponsors for supporting this project! 😄




calc() function and also pushed for improvements in the animation APIs.Freya 0.1, 0.2 and 0.3 were based on the core crates of Dioxus. Starting 0.4 Freya does no longer use Dioxus, instead it uses its own reactive core, partially inspired by Dioxus but yet with lots of differences.
A Claude Code skill with Freya best practices and patterns is available. Install it with:
/plugin marketplace add marc2332/freya
/plugin install freya@freya-marketplace
For other AI coding agents, you can use the skill document directly as context: plugins/freya/skills/freya/SKILL.md
(top 24 of 79)
5,482 followers · starred Aug 2026
1,061 followers · starred Apr 2025
326 followers · starred Jun 2025
5,444 followers · starred Jan 2025