# Picnic builds text tables with a Kotlin DSL and cell level styling

> Picnic is a Kotlin DSL and Java builder API for constructing HTML-like tables that render as text, and it starts completely unstyled so that every border, padding and alignment decision is explicit. The interesting parts are the cascade, where cell, row and table styles override each other, and the span handling, where a rowspan pushes the cells of the next row to the right.

**JakeWharton/picnic** — A Kotlin DSL and Java/Kotlin builder API for constructing HTML-like tables which can be rendered to text

- Repository: https://github.com/JakeWharton/picnic
- Stars: 976 · Forks: 34
- Language: Kotlin
- License: Apache-2.0
- Published: 2026-10-20 · Updated: 2026-10-20 · Language: en
- Canonical page: https://hysenlabs.com/projects/jakewharton-picnic

## Unstyled on purpose

The first thing the documentation does is show you the least impressive possible output, and then explain why. A table built with two rows of two cells each renders as HelloWorld and Hola Mundo, with no separators and no spacing, and the reaction invited is that it is very underwhelming. The reason given is that Picnic tables start completely unstyled by design. That single sentence tells you how the library is built, because every visual decision in the output is something the caller asked for rather than a default the library picked. The feature list is correspondingly small and concrete: borders with multiple styles, padding for left, right, top and bottom, per-table, row and cell styling, header and footer sections, row and column spans, and text alignment. There is no theme, no preset and no markup, which makes the library easy to reason about and easy to embed in a tool that already has its own conventions.

## Two places to set a style, and they mean different things

The border example is the shortest demonstration of the design. Styling is applied through a cellStyle block, and the comment in that block says the options affect the style of all cells contained within the table:

```kotlin
table {
  cellStyle {
    // These options affect the style of all cells contained within the table.
    border = true
  }
  row("Hello", "World")
  row("Hola", "Mundo")
}
```

A table level style block exists as well, and the difference is stated in a comment inside it, that unlike cellStyle these options affect the style of the table itself. The example sets the borderStyle to Hidden there while leaving border on the cells, and the result is a table with no outer frame but with a vertical separator and a horizontal rule between the rows. That separation is the point of the two blocks, since the border drawing is a property of the table, while the presence of a border on a cell is a property of the cell. Setting the table border style to Hidden while leaving the cell border on is how you get a clean columnar layout with no box around it.

## Padding and the style cascade

Padding is set the same way, with paddingLeft and paddingRight on the cell style, and the effect is one space of margin inside each cell border. The more interesting part of the styling model is the cascade, which the documentation shows by nesting. A row can carry its own cellStyle, and the comment says those options affect only the cells contained within this row and override the table options. A single cell can then carry its own options, and the comment there says they affect only this specific cell and override row and table options. So there are three levels, table, row and cell, and the innermost wins. The documented example sets padding on the table, then turns paddingTop on for one row only, then turns the border off for one cell, and the rendered result shows all three effects at once, with a cell that has no right or bottom border. For a library that prints to a terminal, that kind of surgical control is the whole point, because you often need a separator between groups without a full grid.

## Headers and footers keep their position

Header and footer sections are separate blocks in the builder, and the ordering rule is stated in the comments. Rows in a header always come first no matter when they are added, and rows in a footer always come last. That is a small but meaningful guarantee, because it means you can declare the header at the top of your builder and the body rows after it without thinking about emission order, and the same for a footer at the end. The example shows a header row, a footer row and a normal row, with the border turned on afterwards in a cellStyle block, which is worth noticing because the cell style applies to the header and footer rows as well. Row and column spans are the other structural feature. A cell with a rowSpan of 2 occupies two rows, and the following row then has one fewer visible cell because the spanning cell carries over and pushes the rest to the right. A cell with a columnSpan of 2 consumes two columns, which is why a row containing only that cell still fills the full width.

## Alignment is a cell property

Text alignment is set in the same style blocks, using values such as TopCenter and MiddleLeft, and it is what makes a spanned cell look intentional rather than broken. The alignment example sets a cell style with the alignment at TopCenter, then gives one cell a rowSpan of 4 and an alignment of MiddleLeft, so the text in that tall cell sits in the vertical middle while the single-line cells beside it sit at the top. The same trick is applied a second time further down the table, which is how the example demonstrates two independent spans in one table without the two interfering. The enum names combine a vertical position with a horizontal one, and the fact that alignment is per cell rather than per table is what allows a header row and a numeric column to be laid out differently inside the same table.

## A real report, and how to get the library

The documentation closes with a real-world example taken from the Diffuse tool, and it is worth reading because it shows the features combined rather than in isolation. That table uses row and column spans, headers and footers, borders, a table border style, padding and text alignment, and the output is an APK size comparison with compressed and uncompressed columns and a diff column, with a totals row in the footer. That is the kind of output the library exists for, a report that a human reads in a terminal. Getting the library is a standard Gradle dependency:

```groovy
repositories {
  mavenCentral()
}
dependencies {
  implementation 'com.jakewharton.picnic:picnic:0.7.0'
}
```

and a snapshot of the development version is published through the Central Portal Snapshots repository:

```groovy
repositories {
  maven {
    url 'https://central.sonatype.com/repository/maven-snapshots/'
  }
}
dependencies {
  implementation 'com.jakewharton.picnic:picnic:0.8.0-SNAPSHOT'
}
```

The Java side of the API is the builder form of the same model, since the project is described as a Kotlin DSL and a Java builder API rather than a Kotlin only library.

## A slow release cadence and a still moving repository

The project metadata is small and mostly about the build. The default branch is trunk, the top level holds a build.gradle, a settings.gradle, a gradle.properties and the Gradle wrapper, and there is a picnic/ directory for the library itself, a sample/ directory with its own build file and sources, and a kotlin-js-store/ directory, which points at the multiplatform ambitions of the project. There is also a RELEASING.md file, a CHANGELOG.md and an .editorconfig. The release history is the thing to look at carefully. The listed versions are 0.7.0 from 2023-08-30 and 0.6.0 from 2022-02-26, and the repository is not archived with the last push on 2026-10-16, so the code is still being worked on while the last published artifact is more than three years old. For a small library with a narrow surface that is a reasonable trade, but it means you are likely to depend on the 0.7.0 release or on a snapshot rather than on a version with a long support history.

## Conclusion

Picnic fits a Kotlin or Java codebase that has to print a table to a terminal, whether that is a diff report, a size comparison or a test result, and it fits a team that wants the layout expressed as code rather than assembled from string concatenation, because the style cascade and the span arithmetic are handled for you. It does not fit a project that needs wide characters handled correctly, since the library measures the text as it is given, and it does not fit a team that wants a maintained dependency, because the last release is 0.7.0 from 2023-08-30 and the last push was on 2026-10-16. Before you depend on it, check the sample project in the repository, and note that a snapshot of the development version is published as 0.8.0-SNAPSHOT through the Central Portal Snapshots repository if you want to try the current code rather than the 0.7.0 release.

## FAQ

### What is Picnic?

It is a Kotlin DSL and Java builder API for constructing HTML-like tables which can be rendered to text, with borders, padding, per-table, row and cell styling, header and footer sections, row and column spans, and text alignment.

### Why does my Picnic table have no borders?

Tables start completely unstyled by design. You have to switch the border on yourself in a cellStyle block, and the table level style block controls the border drawing of the table itself, where borderStyle can be set to Hidden to remove the outer frame while keeping the cell borders.

### How do I set a different style for one cell in Picnic?

Styles nest and the innermost wins. Table level cellStyle options apply to every cell, a row level cellStyle overrides them for that row, and options set on an individual cell override the row and table options.

### How do I add Picnic to a Gradle project?

Add the dependency com.jakewharton.picnic:picnic:0.7.0 from mavenCentral, or use the snapshot com.jakewharton.picnic:picnic:0.8.0-SNAPSHOT from the Central Portal Snapshots repository for the development version.

### Is Picnic still maintained?

The repository is not archived and the last push was on 2026-10-16, but the newest published release is 0.7.0 from 2023-08-30, after 0.6.0 in February 2022.

## Sources

- [Issues](https://github.com/JakeWharton/picnic/issues)
- [JakeWharton/picnic on GitHub](https://github.com/JakeWharton/picnic)
- [License: Apache-2.0](https://github.com/JakeWharton/picnic/blob/trunk/LICENSE)
- [README](https://github.com/JakeWharton/picnic/blob/trunk/README.md)
- [Releases](https://github.com/JakeWharton/picnic/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/jakewharton-picnic
