<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Vitalii Shevchuk</title>
    <description>The latest articles on DEV Community by Vitalii Shevchuk (@vitalii_shevchuk_de4862c7).</description>
    <link>https://dev.to/vitalii_shevchuk_de4862c7</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2378233%2F9f65f1bd-3e86-4312-be78-db9d73684db7.png</url>
      <title>DEV Community: Vitalii Shevchuk</title>
      <link>https://dev.to/vitalii_shevchuk_de4862c7</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/vitalii_shevchuk_de4862c7"/>
    <language>en</language>
    <item>
      <title>I Built an Open-Source JavaScript Data Grid Because Tables Never Stay Simple</title>
      <dc:creator>Vitalii Shevchuk</dc:creator>
      <pubDate>Sat, 13 Jun 2026 01:51:49 +0000</pubDate>
      <link>https://dev.to/vitalii_shevchuk_de4862c7/i-built-an-open-source-javascript-data-grid-because-tables-never-stay-simple-4bgd</link>
      <guid>https://dev.to/vitalii_shevchuk_de4862c7/i-built-an-open-source-javascript-data-grid-because-tables-never-stay-simple-4bgd</guid>
      <description>&lt;p&gt;Every data-heavy product has &lt;em&gt;that&lt;/em&gt; table.&lt;/p&gt;

&lt;p&gt;It begins innocently:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Just show these records in rows."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Then the requests arrive.&lt;/p&gt;

&lt;p&gt;Can users edit cells? Pin columns? Filter 100,000 rows? Navigate without a mouse? Export to CSV? Add formulas? Use it in Angular too?&lt;/p&gt;

&lt;p&gt;At some point, the table is no longer a table. It is a small application inside your application.&lt;/p&gt;

&lt;p&gt;That recurring problem is why I built &lt;a href="https://ace-grid.com" rel="noopener noreferrer"&gt;Ace Grid&lt;/a&gt;, a spreadsheet-grade JavaScript data grid with a free, MIT-licensed Core.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem I wanted to solve
&lt;/h2&gt;

&lt;p&gt;Teams usually face two imperfect options:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Keep adding features to a basic table until it becomes difficult to maintain.&lt;/li&gt;
&lt;li&gt;Adopt a large grid platform before the product actually needs all of it.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Ace Grid takes a gradual approach: start with the practical data-grid foundation, then add spreadsheet and server-backed capabilities only when your use case requires them.&lt;/p&gt;

&lt;p&gt;No feature maze in the first example. No need to adopt the whole platform on day one. Just a usable grid.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the free Core includes
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.npmjs.com/package/@ace-grid/core" rel="noopener noreferrer"&gt;&lt;code&gt;@ace-grid/core&lt;/code&gt;&lt;/a&gt; is the free React runtime. It includes the features that tend to turn a table into a real product workflow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Inline cell editing and keyboard navigation&lt;/li&gt;
&lt;li&gt;Cell and range selection&lt;/li&gt;
&lt;li&gt;Sorting, filtering, search, and pagination&lt;/li&gt;
&lt;li&gt;Column resizing, reordering, and pinning&lt;/li&gt;
&lt;li&gt;Virtualized rendering for large datasets&lt;/li&gt;
&lt;li&gt;CSV import and export&lt;/li&gt;
&lt;li&gt;Themes and custom cell renderers&lt;/li&gt;
&lt;li&gt;Serializable, schema-aware grid state&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The package is MIT licensed, and the Core source is public on &lt;a href="https://github.com/Vitashev/ace-grid-core" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The smallest useful example
&lt;/h2&gt;

&lt;p&gt;Install Core with its React peer dependencies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @ace-grid/core react react-dom
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then give the grid rows, columns, and a height:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Grid&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@ace-grid/core&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;company&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;HelioBank&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;segment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Enterprise&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;company&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Northstar AI&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;segment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Strategic&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;columns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;company&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Company&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;editable&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;segment&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Segment&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;filterable&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;CustomersGrid&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Grid&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;columns&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;layout&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;520&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the intended starting point. You should not need to understand the entire grid system before rendering useful data.&lt;/p&gt;

&lt;h2&gt;
  
  
  What if the app is not React?
&lt;/h2&gt;

&lt;p&gt;Ace Grid uses one shared runtime and provides thin integration packages for other stacks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://ace-grid.com/guides/angular-data-grid" rel="noopener noreferrer"&gt;Angular&lt;/a&gt;: &lt;code&gt;@ace-grid/angular&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://ace-grid.com/guides/vue-data-grid" rel="noopener noreferrer"&gt;Vue 3&lt;/a&gt;: &lt;code&gt;@ace-grid/vue&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://ace-grid.com/guides/svelte-data-grid" rel="noopener noreferrer"&gt;Svelte&lt;/a&gt;: &lt;code&gt;@ace-grid/svelte&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://ace-grid.com/guides/web-component-data-grid" rel="noopener noreferrer"&gt;Web Components&lt;/a&gt;: &lt;code&gt;@ace-grid/wc&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This avoids building five separate grids that slowly develop five different sets of bugs and behavior.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Core ends
&lt;/h2&gt;

&lt;p&gt;I wanted the licensing boundary to be understandable without a sales call.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Core&lt;/strong&gt; is the free everyday foundation: editing, selection, filtering, pinning, virtualization, theming, and CSV workflows.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pro&lt;/strong&gt; adds spreadsheet-oriented work such as formulas, validation, Excel I/O, grouping, tree data, spanning, and sparklines.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Enterprise&lt;/strong&gt; adds server-backed analytics such as the server row model, pivoting, charts, and master-detail views.&lt;/p&gt;

&lt;p&gt;The idea is simple: use Core for free. Upgrade only if the advanced workflow creates enough value for your product.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I learned while building it
&lt;/h2&gt;

&lt;p&gt;The hardest part of a data grid is not drawing rectangles.&lt;/p&gt;

&lt;p&gt;It is making dozens of interactions agree with each other:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What happens to selection after sorting?&lt;/li&gt;
&lt;li&gt;How should pinned columns behave during resize?&lt;/li&gt;
&lt;li&gt;Does keyboard navigation still make sense inside a custom editor?&lt;/li&gt;
&lt;li&gt;Can grid state be saved, restored, and validated?&lt;/li&gt;
&lt;li&gt;Do framework wrappers behave like the underlying runtime?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A grid feels simple only when those decisions are consistent. That consistency is where most of the engineering work lives.&lt;/p&gt;

&lt;h2&gt;
  
  
  I would value blunt feedback
&lt;/h2&gt;

&lt;p&gt;Ace Grid is available now, and I am looking for feedback from developers building admin tools, analytics products, internal platforms, CRMs, and other interfaces where "just a table" becomes a core workflow.&lt;/p&gt;

&lt;p&gt;In particular:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is the first implementation easy to understand?&lt;/li&gt;
&lt;li&gt;Which missing Core feature would block adoption?&lt;/li&gt;
&lt;li&gt;Which migration guide would save you the most time?&lt;/li&gt;
&lt;li&gt;Which framework needs deeper examples?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Try the &lt;a href="https://ace-grid.com" rel="noopener noreferrer"&gt;live site&lt;/a&gt;, read the &lt;a href="https://ace-grid.com/docs" rel="noopener noreferrer"&gt;documentation&lt;/a&gt;, inspect the &lt;a href="https://ace-grid.com/api" rel="noopener noreferrer"&gt;API reference&lt;/a&gt;, or review the &lt;a href="https://github.com/Vitashev/ace-grid-core" rel="noopener noreferrer"&gt;Core source&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;And if your current table has already become a small application, I would genuinely like to hear what made it cross that line.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Disclosure: AI assistance was used to edit and structure this post. Product details and code examples were verified against the Ace Grid documentation and source.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>opensource</category>
      <category>webdev</category>
      <category>react</category>
    </item>
  </channel>
</rss>
