diff --git a/Cargo.lock b/Cargo.lock
index fb91072..28e29f2 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -926,6 +926,18 @@ dependencies = [
  "windows-sys 0.48.0",
 ]
 
+[[package]]
+name = "comfy-table"
+version = "7.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ab77dbd8adecaf3f0db40581631b995f312a8a5ae3aa9993188bb8f23d83a5b"
+dependencies = [
+ "crossterm 0.26.1",
+ "strum 0.24.1",
+ "strum_macros",
+ "unicode-width",
+]
+
 [[package]]
 name = "concurrent-queue"
 version = "2.3.0"
@@ -1038,6 +1050,22 @@ dependencies = [
  "winapi",
 ]
 
+[[package]]
+name = "crossterm"
+version = "0.26.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a84cda67535339806297f1b331d6dd6320470d2a0fe65381e79ee9e156dd3d13"
+dependencies = [
+ "bitflags 1.3.2",
+ "crossterm_winapi",
+ "libc",
+ "mio",
+ "parking_lot",
+ "signal-hook",
+ "signal-hook-mio",
+ "winapi",
+]
+
 [[package]]
 name = "crossterm_winapi"
 version = "0.9.1"
@@ -2496,7 +2524,7 @@ version = "0.3.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "4c2a1e77b5cd714b04247ad912b7c8fe9a1fe1d58425048249def91bcf690e4c"
 dependencies = [
- "crossterm",
+ "crossterm 0.25.0",
  "qrcode",
 ]
 
@@ -2844,6 +2872,12 @@ dependencies = [
  "untrusted",
 ]
 
+[[package]]
+name = "rustversion"
+version = "1.0.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4"
+
 [[package]]
 name = "ryu"
 version = "1.0.15"
@@ -2899,7 +2933,7 @@ dependencies = [
  "serde",
  "serde_json",
  "sqlx",
- "strum",
+ "strum 0.25.0",
  "thiserror",
  "time",
  "tracing",
@@ -3537,12 +3571,31 @@ version = "0.10.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
 
+[[package]]
+name = "strum"
+version = "0.24.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f"
+
 [[package]]
 name = "strum"
 version = "0.25.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125"
 
+[[package]]
+name = "strum_macros"
+version = "0.24.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59"
+dependencies = [
+ "heck",
+ "proc-macro2",
+ "quote",
+ "rustversion",
+ "syn 1.0.109",
+]
+
 [[package]]
 name = "subtle"
 version = "2.5.0"
@@ -3592,9 +3645,10 @@ dependencies = [
 
 [[package]]
 name = "tfcli"
-version = "0.2.0"
+version = "0.2.1"
 dependencies = [
  "clap",
+ "comfy-table",
  "dirs",
  "ipnet",
  "qr2term",
@@ -4003,6 +4057,12 @@ version = "1.10.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36"
 
+[[package]]
+name = "unicode-width"
+version = "0.1.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85"
+
 [[package]]
 name = "unicode_categories"
 version = "0.1.1"
diff --git a/tfcli/Cargo.toml b/tfcli/Cargo.toml
index 7b4fb23..8fcf1ce 100644
--- a/tfcli/Cargo.toml
+++ b/tfcli/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "tfcli"
-version = "0.2.0"
+version = "0.2.1"
 edition = "2021"
 description = "Command-line client for managing trifid-api"
 license = "GPL-3.0-or-later"
@@ -20,3 +20,4 @@ dirs = "5"
 qr2term = "0.3"
 ipnet = "2.7"
 serde_json = "1"
+comfy-table = "7"
diff --git a/tfcli/src/main.rs b/tfcli/src/main.rs
index adb6529..e3cc317 100644
--- a/tfcli/src/main.rs
+++ b/tfcli/src/main.rs
@@ -1,7 +1,8 @@
 use std::error::Error;
+use std::fmt::{Display, Formatter};
 use std::fs;
 use std::net::{Ipv4Addr, SocketAddrV4};
-use clap::{Parser, Subcommand};
+use clap::{Parser, Subcommand, ValueEnum};
 use ipnet::Ipv4Net;
 use url::Url;
 use crate::account::account_main;
@@ -93,7 +94,10 @@ pub enum AccountCommands {
 #[derive(Subcommand, Debug)]
 pub enum NetworkCommands {
     /// List all networks associated with your trifid account.
-    List {},
+    List {
+        #[clap(short = 'T', long, default_value_t = TableStyle::Basic)]
+        table_style: TableStyle
+    },
     /// Lookup a specific network by ID.
     Lookup {
         #[clap(short, long)]
@@ -123,7 +127,10 @@ pub enum RoleCommands {
         rules_json: String
     },
     /// List all roles attached to your organization
-    List {},
+    List {
+        #[clap(short = 'T', long, default_value_t = TableStyle::Basic)]
+        table_style: TableStyle
+    },
     /// Lookup a specific role by it's ID
     Lookup {
         #[clap(short, long)]
@@ -240,6 +247,27 @@ pub enum HostOverrideCommands {
     }
 }
 
+#[derive(Debug, Clone, ValueEnum)]
+pub enum TableStyle {
+    List,
+    Basic,
+    Pretty
+}
+impl Default for TableStyle {
+    fn default() -> Self {
+        Self::Basic
+    }
+}
+impl Display for TableStyle {
+    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+        match self {
+            Self::List => write!(f, "list"),
+            Self::Basic => write!(f, "basic"),
+            Self::Pretty => write!(f, "pretty")
+        }
+    }
+}
+
 #[tokio::main]
 async fn main() {
     match main2().await {
diff --git a/tfcli/src/network.rs b/tfcli/src/network.rs
index 6a59574..e4db499 100644
--- a/tfcli/src/network.rs
+++ b/tfcli/src/network.rs
@@ -1,13 +1,16 @@
 use std::error::Error;
 use std::fs;
+use comfy_table::modifiers::UTF8_ROUND_CORNERS;
+use comfy_table::presets::UTF8_FULL;
+use comfy_table::Table;
 use serde::Deserialize;
 use url::Url;
 use crate::api::APIErrorResponse;
-use crate::NetworkCommands;
+use crate::{NetworkCommands, TableStyle};
 
 pub async fn network_main(command: NetworkCommands, server: Url) -> Result<(), Box<dyn Error>> {
     match command {
-        NetworkCommands::List {} => list_networks(server).await,
+        NetworkCommands::List { table_style } => list_networks(server, table_style).await,
         NetworkCommands::Lookup {id} => get_network(id, server).await
     }
 }
@@ -32,7 +35,7 @@ pub struct Network {
     pub name: String
 }
 
-pub async fn list_networks(server: Url) -> Result<(), Box<dyn Error>> {
+pub async fn list_networks(server: Url, table_style: TableStyle) -> Result<(), Box<dyn Error>> {
     let client = reqwest::Client::new();
 
     // load session token
@@ -48,20 +51,39 @@ pub async fn list_networks(server: Url) -> Result<(), Box<dyn Error>> {
     if res.status().is_success() {
         let resp: NetworkListResp = res.json().await?;
 
-        for network in &resp.data {
-            println!("         Network: {}", network.id);
-            println!("            CIDR: {}", network.cidr);
-            println!("    Organization: {}", network.organization_id);
-            println!("      Signing CA: {}", network.signing_ca_id);
-            println!("Dedicated Relays: {}", !network.lighthouses_as_relays);
-            println!("            Name: {}", network.name);
-            println!("      Created At: {}", network.created_at);
-            println!();
-        }
-
         if resp.data.is_empty() {
             println!("No networks found");
+            return Ok(());
         }
+
+        if matches!(table_style, TableStyle::List) {
+            for network in &resp.data {
+                println!("         Network: {}", network.id);
+                println!("            CIDR: {}", network.cidr);
+                println!("    Organization: {}", network.organization_id);
+                println!("      Signing CA: {}", network.signing_ca_id);
+                println!("Dedicated Relays: {}", !network.lighthouses_as_relays);
+                println!("            Name: {}", network.name);
+                println!("      Created At: {}", network.created_at);
+                println!();
+            }
+            return Ok(());
+        }
+
+        let mut table = Table::new();
+        match table_style {
+            TableStyle::List => unreachable!(),
+            TableStyle::Basic => (),
+            TableStyle::Pretty => { table.load_preset(UTF8_FULL).apply_modifier(UTF8_ROUND_CORNERS) ; },
+        };
+
+        table.set_header(vec!["ID", "Name", "CIDR", "Organization ID", "Signing CA ID", "Dedicated Relays", "Created At"]);
+
+        for network in &resp.data {
+            table.add_row(vec![&network.id, &network.name, &network.cidr, &network.organization_id, &network.signing_ca_id, (!network.lighthouses_as_relays).to_string().as_str(), &network.created_at]);
+        }
+
+        println!("{table}");
     } else {
         let resp: APIErrorResponse = res.json().await?;
 
diff --git a/tfcli/src/role.rs b/tfcli/src/role.rs
index a6ffd9c..441b1f9 100644
--- a/tfcli/src/role.rs
+++ b/tfcli/src/role.rs
@@ -1,13 +1,16 @@
 use std::error::Error;
 use std::fs;
+use comfy_table::modifiers::UTF8_ROUND_CORNERS;
+use comfy_table::presets::UTF8_FULL;
+use comfy_table::Table;
 use serde::{Deserialize, Serialize};
 use url::Url;
 use crate::api::APIErrorResponse;
-use crate::{RoleCommands};
+use crate::{RoleCommands, TableStyle};
 
 pub async fn role_main(command: RoleCommands, server: Url) -> Result<(), Box<dyn Error>> {
     match command {
-        RoleCommands::List {} => list_roles(server).await,
+        RoleCommands::List { table_style } => list_roles(server, table_style).await,
         RoleCommands::Lookup {id} => get_role(id, server).await,
         RoleCommands::Create { name, description, rules_json } => create_role(name, description, rules_json, server).await,
         RoleCommands::Delete { id } => delete_role(id, server).await,
@@ -47,7 +50,7 @@ pub struct RoleFirewallRulePortRange {
     pub to: u16
 }
 
-pub async fn list_roles(server: Url) -> Result<(), Box<dyn Error>> {
+pub async fn list_roles(server: Url, table_style: TableStyle) -> Result<(), Box<dyn Error>> {
     let client = reqwest::Client::new();
 
     // load session token
@@ -63,22 +66,40 @@ pub async fn list_roles(server: Url) -> Result<(), Box<dyn Error>> {
     if res.status().is_success() {
         let resp: RoleListResp = res.json().await?;
 
-        for role in &resp.data {
-            println!("            Role: {} ({})", role.name, role.id);
-            println!("     Description: {}", role.description);
-            for rule in &role.firewall_rules {
-                println!("Rule Description: {}", rule.description);
-                println!("    Allowed Role: {}", rule.allowed_role_id.as_ref().unwrap_or(&"All roles".to_string()));
-                println!("        Protocol: {}", rule.protocol);
-                println!("      Port Range: {}", if let Some(pr) = rule.port_range.as_ref() { format!("{}-{}", pr.from, pr.to) } else { "Any".to_string() });
-            }
-            println!("         Created: {}", role.created_at);
-            println!("         Updated: {}", role.modified_at);
-        }
-
         if resp.data.is_empty() {
             println!("No roles found");
+            return Ok(());
         }
+
+        if matches!(table_style, TableStyle::List) {
+            for role in &resp.data {
+                println!("            Role: {} ({})", role.name, role.id);
+                println!("     Description: {}", role.description);
+                for rule in &role.firewall_rules {
+                    println!("Rule Description: {}", rule.description);
+                    println!("    Allowed Role: {}", rule.allowed_role_id.as_ref().unwrap_or(&"All roles".to_string()));
+                    println!("        Protocol: {}", rule.protocol);
+                    println!("      Port Range: {}", if let Some(pr) = rule.port_range.as_ref() { format!("{}-{}", pr.from, pr.to) } else { "Any".to_string() });
+                }
+                println!("         Created: {}", role.created_at);
+                println!("         Updated: {}", role.modified_at);
+            }
+            return Ok(());
+        }
+
+        let mut table = Table::new();
+        match table_style {
+            TableStyle::List => unreachable!(),
+            TableStyle::Basic => (),
+            TableStyle::Pretty => { table.load_preset(UTF8_FULL).apply_modifier(UTF8_ROUND_CORNERS); },
+        };
+
+        table.set_header(vec!["ID", "Name", "Description", "Rule Count", "Created", "Updated"]);
+        for role in &resp.data {
+            table.add_row(vec![&role.id, &role.name, &role.description, role.firewall_rules.len().to_string().as_str(), &role.created_at, &role.modified_at]);
+        }
+
+        println!("{table}");
     } else {
         let resp: APIErrorResponse = res.json().await?;