diff --git a/Cargo.lock b/Cargo.lock
index cf17f056..8863337e 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -496,6 +496,7 @@ dependencies = [
  "tree-sitter",
  "unicode-segmentation",
  "unicode-width",
+ "xdg",
 ]
 
 [[package]]
@@ -1390,3 +1391,9 @@ name = "winapi-x86_64-pc-windows-gnu"
 version = "0.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
+[[package]]
+name = "xdg"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d089681aa106a86fade1b0128fb5daf07d5867a509ab036d99988dec80429a57"
diff --git a/helix-core/Cargo.toml b/helix-core/Cargo.toml
index d95c0f47..4b1566d7 100644
--- a/helix-core/Cargo.toml
+++ b/helix-core/Cargo.toml
@@ -21,3 +21,5 @@ once_cell = "1.4"
 regex = "1"
 
 serde = { version = "1.0", features = ["derive"] }
+
+xdg = "2.0"
diff --git a/helix-core/src/lib.rs b/helix-core/src/lib.rs
index b3ce3c47..dda9863b 100644
--- a/helix-core/src/lib.rs
+++ b/helix-core/src/lib.rs
@@ -44,6 +44,13 @@ pub(crate) fn find_first_non_whitespace_char(text: RopeSlice, line_num: usize) -
     None
 }
 
+pub fn config_dir() -> std::path::PathBuf {
+    // TODO: allow env var override
+    let xdg_dirs =
+        xdg::BaseDirectories::with_prefix("helix").expect("Unable to find XDG directories!");
+    xdg_dirs.get_config_home()
+}
+
 pub use ropey::{Rope, RopeSlice};
 
 pub use tendril::StrTendril as Tendril;
diff --git a/helix-term/src/main.rs b/helix-term/src/main.rs
index a2ea5993..612f8cf0 100644
--- a/helix-term/src/main.rs
+++ b/helix-term/src/main.rs
@@ -74,10 +74,17 @@ fn main() {
     setup_logging(verbosity).expect("failed to initialize logging.");
 
     // initialize language registry
+    use helix_core::config_dir;
     use helix_core::syntax::{Loader, LOADER};
-    let toml = include_str!("../../languages.toml");
+
+    // load $HOME/.config/helix/languages.toml, fallback to default config
+    let config = std::fs::read(config_dir().join("languages.toml"));
+    let toml = config
+        .as_deref()
+        .unwrap_or(include_bytes!("../../languages.toml"));
+
     LOADER.get_or_init(|| {
-        let config = toml::from_str(toml).expect("Could not parse languages.toml");
+        let config = toml::from_slice(toml).expect("Could not parse languages.toml");
         Loader::new(config)
     });
 
diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs
index 531571f7..0eab4fe7 100644
--- a/helix-view/src/editor.rs
+++ b/helix-view/src/editor.rs
@@ -25,9 +25,13 @@ pub enum Action {
 
 impl Editor {
     pub fn new(executor: &'static smol::Executor<'static>, mut area: tui::layout::Rect) -> Self {
-        // TODO: load from config dir
-        let toml = include_str!("../../theme.toml");
-        let theme: Theme = toml::from_str(&toml).expect("failed to parse theme.toml");
+        use helix_core::config_dir;
+        let config = std::fs::read(config_dir().join("theme.toml"));
+        // load $HOME/.config/helix/theme.toml, fallback to default config
+        let toml = config
+            .as_deref()
+            .unwrap_or(include_bytes!("../../theme.toml"));
+        let theme: Theme = toml::from_slice(&toml).expect("failed to parse theme.toml");
 
         let language_servers = helix_lsp::Registry::new();