diff --git a/src/main/java/com/univamu/svg/Attribute.java b/src/main/java/com/univamu/svg/Attribute.java
new file mode 100644
index 0000000000000000000000000000000000000000..f9ecd5ea13db031df712c87fd3791ad841535c5b
--- /dev/null
+++ b/src/main/java/com/univamu/svg/Attribute.java
@@ -0,0 +1,44 @@
+package com.univamu.svg;
+
+public interface Attribute {
+
+  String name();
+  String stringValue();
+
+  default String asSvg() {
+    return name() + "=\"" + stringValue() + "\"";
+  }
+
+  static Attribute tag(String name, String value) {
+    return new Tag(name, value);
+  }
+
+  static Attribute tag(String name, double value) {
+    return new Tag(name, String.valueOf(value));
+  }
+  static Attribute tag(String name, int value) {
+    return new Tag(name, String.valueOf(value));
+  }
+  static Attribute tag(String name, boolean value) {
+    return new Tag(name, String.valueOf(value));
+  }
+  static Attribute tag(String name, float value) {
+    return new Tag(name, String.valueOf(value));
+  }
+  static Attribute tag(String name, long value) {
+    return new Tag(name, String.valueOf(value));
+  }
+  static Attribute tag(String name, char value) {
+    return new Tag(name, String.valueOf(value));
+  }
+  static Attribute tag(String name, short value) {
+    return new Tag(name, String.valueOf(value));
+  }
+  static Attribute tag(String name, byte value) {
+    return new Tag(name, String.valueOf(value));
+  }
+
+  static Attribute tag(String name, Object value) {
+    return new Tag(name, value.toString());
+  }
+}
diff --git a/src/main/java/com/univamu/svg/Node.java b/src/main/java/com/univamu/svg/Node.java
new file mode 100644
index 0000000000000000000000000000000000000000..10ac3fe98c7f40e255690f1c07072e79cff29d5a
--- /dev/null
+++ b/src/main/java/com/univamu/svg/Node.java
@@ -0,0 +1,63 @@
+package com.univamu.svg;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static com.univamu.svg.Attribute.tag;
+
+public class Node {
+
+  private final String name;
+  private final Map<String, Attribute> attributes = new HashMap<>();
+  private final List<Node> children = new ArrayList<>();
+
+  public Node(String name) {
+    this.name = name;
+  }
+
+  public Node add(Attribute attribute) {
+    attributes.put(attribute.name(),attribute);
+    return this; // allows chaining of adds
+  }
+
+  public List<Node> children() {
+    return children;
+  }
+
+  public Node add(Node node) {
+    this.children.add(node);
+    return this; // allows chaining of adds
+  }
+
+  public String toString() {
+    StringBuilder builder = new StringBuilder();
+    this.appendTo(builder,0);
+    return builder.toString();
+  }
+
+  private void appendTo(StringBuilder builder, int indentation) {
+    builder.append("  ".repeat(indentation)).append("<").append(name);
+    attributes.values().forEach(attribute ->
+        builder.append(" ").append(attribute.asSvg())
+    );
+    if (children.isEmpty()) {
+      builder.append("/>\n");
+    } else {
+      builder.append(">\n");
+      children.forEach(child -> child.appendTo(builder,indentation+1));
+      builder.append("  ".repeat(indentation)).append("</").append(name).append(">\n");
+    }
+  }
+
+
+  public void toFile(String filepath) throws FileNotFoundException {
+    try (PrintStream out = new PrintStream(new File(filepath))) {
+      out.println(this);
+    }
+  }
+}
diff --git a/src/main/java/com/univamu/svg/Tag.java b/src/main/java/com/univamu/svg/Tag.java
new file mode 100644
index 0000000000000000000000000000000000000000..17344ba9803b1140280e58dcd9db2558d967330b
--- /dev/null
+++ b/src/main/java/com/univamu/svg/Tag.java
@@ -0,0 +1,5 @@
+package com.univamu.svg;
+
+public record Tag(String name, String stringValue) implements Attribute {
+
+}