# CSS를 질의 언어로 보기

> Clean Markdown view of GeekNews topic #28897. Use the original source for factual precision when an external source URL is present.

## Metadata

- GeekNews HTML: [https://news.hada.io/topic?id=28897](https://news.hada.io/topic?id=28897)
- GeekNews Markdown: [https://news.hada.io/topic/28897.md](https://news.hada.io/topic/28897.md)
- Type: GN+
- Author: [xguru](https://news.hada.io/@xguru)
- Published: 2026-04-26T11:17:16+09:00
- Updated: 2026-04-26T11:17:16+09:00
- Original source: [evdc.me](https://evdc.me/blog/css-query)
- Points: 1
- Comments: 1

## Topic Body

- **선택자와 규칙**으로 대상 집합을 고르고 속성을 적용하는 CSS의 구조는, 집합과 규칙으로 동작하는 Datalog와 형태적으로 닮아 있음  
- `div.awesome` 같은 선택자 결합은 **교집합**을 만들고, Datalog에서는 같은 변수를 반복하는 방식으로 비슷한 **join**이 일어남  
- 현재 CSS는 계산된 스타일 결과를 다시 선택 조건으로 삼지 못해, **재귀적 전이 질의**나 파생 상태의 반복 전파를 직접 표현하기 어려움  
- Datalog은 **재귀 규칙**과 **고정점 평가**로 새 사실이 더 이상 생기지 않을 때까지 관계를 확장하며, 단조성 덕분에 유한한 범위에서 계산을 끝낼 수 있음  
- 실제 CSS는 Container Queries 같은 기능으로 조상 정보를 읽을 수 있지만 **피드백 루프**와 순환을 막는 방향을 택했고, 대신 CSS 문법을 재귀 질의에 접목할 여지는 남아 있음  
  
---  
  
### CSS와 Datalog의 닮은 구조  
- CSS는 **대상 집합 선택**과 **선택된 대상에 대한 규칙 적용**이라는 구조를 가짐  
  - HTML 요소 같은 "Things"가 먼저 존재하고, selector가 공통 속성을 가진 집합을 가리킴  
  - `div`, `#child`, `.awesome`, `[data-custom-attribute="foo"]` 같은 selector로 집합을 기술할 수 있음  
  - `div.awesome`처럼 selector를 결합해 **교집합**을 만들 수 있음  
- CSS 규칙은 selector와 declaration을 묶어, 선택된 요소에 `color`나 `font-size` 같은 속성을 설정함  
  - 다만 이런 속성은 대체로 **언어 바깥의 상태**를 바꾸며, 그 결과를 다시 selector 조건으로 삼지는 못함  
  - `div[color=red]`처럼 스타일 결과를 다시 질의하는 형태는 브라우저가 받아들이지 않음  
- Datalog도 비슷하게 **사실 집합**과 **규칙 기반 도출**로 동작함  
  - `parent(alice, bob)` 같은 원자와 relation이 기본 단위가 됨  
  - 변수 `X`, `Y`를 써서 조건에 맞는 항목 집합을 고를 수 있음  
  - 같은 변수를 반복해 조건을 연결하면 CSS의 selector 결합과 비슷한 **join**이 일어남  
- `head(X, Y) :- body1(X, Z), body2(Z, Y)` 구조는 CSS 규칙과 방향만 반대일 뿐 형태가 유사함  
  - CSS의 selector가 Datalog의 body에 가깝고, declaration은 head에 가까움  
  - `div.awesome { color: red; }`는 `color(X, red) :- div(X), class(X, awesome).`와 대응됨  
  
### CSS가 못 하는 재귀적 질의  
- `data-theme="dark"` 안의 모든 포커스 요소에 반전 스타일을 주되, 중간에 `data-theme="light"`가 나오면 멈추는 조건은 **전이적 질의**를 필요로 함  
  - 실제 CSS에서는 `[data-theme="dark"] :focus`와 `[data-theme="dark"] [data-theme="light"] :focus` 같은 규칙으로 일부만 처리할 수 있음  
  - 중첩 단계가 늘어나면 규칙을 계속 추가해야 하며, 재귀적 관계를 직접 표현하기 어려움  
- 필요한 조건은 요소가 **effectively-dark**인지 재귀적으로 판정하는 일임  
  - 자신이 `data-theme="dark"`이면 effectively-dark가 됨  
  - effectively-dark 조상 아래의 자식도, 중간에 `data-theme="light"`가 없으면 effectively-dark가 됨  
  - 이 상태를 바탕으로 `.effectively-dark :focus`에 스타일을 적용해야 함  
- 가상의 CSSLog 문법에서는 규칙이 `class: +effectively-dark`처럼 **파생 상태를 추가**할 수 있음  
  - `.effectively-dark > :not([data-theme="light"])`가 자식으로 상태를 전파함  
  - 규칙은 목표 상태에 도달할 때까지 **재귀적으로 반복**돼야 함  
- 이런 종류의 재귀 전파는 현재 CSS로는 표현하기 어려움  
  - 글 말미에서 일부 비슷하게 흉내 내는 방법도 나오지만, 같은 원리의 일반 해법은 아님  
  
### Datalog의 재귀와 고정점  
- Datalog은 기존 사실에서 **새 사실을 도출**하는 방식으로 동작하며, 재귀를 기본적으로 다룸  
  - `ancestor(X, Y) :- parent(X, Y).`  
  - `ancestor(X, Y) :- parent(X, Z), ancestor(Z, Y).`  
- `ancestor` 규칙은 부모 관계를 바탕으로 조상 관계를 단계적으로 확장함  
  - `parent(alice, bob)`에서 `ancestor(alice, bob)`가 먼저 생김  
  - 이어서 `alice -> bob -> carol`, `alice -> bob -> dave` 같은 경로도 추가로 도출됨  
- 이 계산은 명시적 `for` 루프 없이도 **고정점 평가**로 끝까지 진행됨  
  - 처음에는 명시된 base fact만 사용함  
  - 모든 규칙의 body를 현재 사실 집합에 대입해 head를 추가함  
  - 새 사실이 더 이상 생기지 않을 때 멈춤  
- 이 방식이 끝나는 이유는 **단조성**에 있음  
  - 사실을 추가만 하고 제거하지 않으므로, 알려진 사실 집합은 계속 커지기만 함  
  - 유한한 사실 집합에서 시작하면 유도 가능한 사실 수도 유한하게 제한됨  
  - 반대로 사실을 제거할 수 있으면 이전 결론이 뒤집히며 무한 루프에 빠질 수 있음  
  
### Container Queries와 실제 CSS의 경계  
- 실제 CSS의 [Container Queries](https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Containment/Container_size_and_style_queries)는 조상이나 컨테이너의 스타일을 기준으로 규칙을 적용할 수 있음  
  - `@container style(--theme: dark) { .card { background: royalblue; color: white; } }` 같은 형태를 지원함  
- 하지만 transitive dark mode 예시는 단순한 조상 조회보다 더 강한 조건을 요구함  
  - 각 요소가 자기 자신이 effectively-dark인지 알아야 함  
  - 그 상태가 자손 전체로 **전이적으로 전파**돼야 함  
  - `data-theme="light"` 경계에서 전파가 멈춰야 함  
- Container Queries는 2번째 조건을 처리하지 못함  
  - 조상의 custom property는 읽을 수 있어도, 다른 규칙이 이미 계산한 **파생 상태**를 다시 질의하지는 못함  
  - DOM에 원래 있던 정보는 볼 수 있지만, 재귀 계산 결과를 selector 조건으로 삼지는 못함  
- 2015년의 [관련 글](https://alistapart.com/article/container-queries-once-more-unto-the-breach/)도 element queries가 같은 문제에 부딪혔다고 짚음  
  - 질의로 설정한 속성을 다시 질의할 수 있게 되면 루프와 무한 반복 위험이 커짐  
- [CSS Working Group](https://www.w3.org/groups/wg/css/)은 이 문제를 **정보 흐름 방향 제한**으로 피해 왔음  
  - 자손이 조상 정보를 질의하는 것은 허용함  
  - 반대 방향 피드백이나 자기 자신 스타일로의 순환은 막음  
  - 그래서 고정점 의미론 없이도 계산을 유한하게 유지함  
  
### CSS 문법을 재귀 질의 언어로 뒤집는 가능성  
- Datalog 의미론을 CSS에 넣기보다, **CSS 문법을 Datalog 위에 얹는 방향**이 더 현실적인 새 길로 제시됨  
  - Datalog의 `:-`, 마침표, 선언 없는 원자 같은 문법은 현대 언어 사용자에게 진입 장벽이 큼  
  - CSS는 이미 트리 구조를 다루는 selector 문법을 풍부하게 갖고 있음  
- 실제 데이터 중에는 **트리 형태**가 많다고 짚음  
  - JSON  
  - AST  
  - 파일시스템  
  - 조직도  
  - XML  
- 이런 영역에서는 부모/자식 관계를 암묵적으로 다루는 CSS풍 문법과 **고정점 재귀**를 결합하면 유용할 수 있음  
  - 일반 Datalog은 트리 구조를 관계형 표현으로 옮겨 적어야 해서 번거로움이 큼  
  - CSS selector 감각을 재귀 질의에 그대로 가져오면 더 많은 프로그래머가 쉽게 접근할 수 있음  
- 이런 형태의 도구는 아직 뚜렷하게 보이지 않음  
  - "CSSLog"라는 이름은 임시적이며, 더 나은 이름의 언어가 나올 수 있음  
  - 재귀적 트리 질의를 더 익숙한 표기법으로 다룰 여지는 남아 있음  
  
### 보충 논점과 참고 링크  
- Datalog은 1970년대부터 관계형 데이터베이스와 당시의 AI 연구 맥락에서 등장했으며, 이후 여러 형태로 반복해서 재등장함  
  - [Datomic](https://www.datomic.com/)  
  - [Differential Datalog](https://github.com/vmware-archive/differential-datalog)  
- 고정점 계산의 단순한 형태는 naive evaluation으로 소개되지만, 이미 알려진 사실을 매번 다시 계산해 비효율적일 수 있음  
  - 각 단계에서 새로 나온 사실만 활용하는 **semi-naive evaluation**이 대표적 개선 방향으로 함께 언급됨  
- 단조성은 분산 시스템에서도 유익한 성질로 이어짐  
  - [Consistency As Logical Monotonicity 소개 1](http://bloom-lang.net/calm/)  
  - [Consistency As Logical Monotonicity 논문](https://arxiv.org/abs/1901.01930)  
- custom property 상속으로 transitive dark mode를 부분적으로 흉내 내는 방법도 있음  
  - `[data-theme="dark"] { --effective-theme: dark; }`  
  - `[data-theme="light"] { --effective-theme: light; }`  
  - `@container style(--effective-theme: dark) { :focus { outline-color: white; } }`  
  - 이 방법은 이 특정 경우에는 대체로 동작하지만, **진짜 전이 폐쇄**를 일반적으로 제공하지는 못함

## Comments



### Comment 56318

- Author: neo
- Created: 2026-04-26T11:17:17+09:00
- Points: 1

###### [Hacker News 의견들](https://news.ycombinator.com/item?id=47893427) 
- **CSS 셀렉터**가 **XPath**보다 훨씬 쓰기 쉬움  
  최근 PHP의 새 DOM API가 HTML과 CSS 셀렉터를 네이티브하게 아주 쉽게 다루게 해준다는 내용으로 발표도 했음. 예전에는 CSS를 XPath로 변환해야 했음  
  [1] [https://speakerdeck.com/keyvan/parsing-html-with-php-8-dot-4...](<https://speakerdeck.com/keyvan/parsing-html-with-php-8-dot-4?slide=40>)  
  브라우저 스타일링 중심으로 발전해 와서, XPath처럼 **텍스트 내용 기반 선택** 같은 기능이 없는 건 아쉬움  
  예전에 제안은 있었지만 브라우저 렌더링 맥락에서 성능 문제가 생길 수 있어 스펙에 못 들어간 걸로 알고 있음
  - **LLM**도 CSS 셀렉터를 꽤 잘 다룸  
    문서 편집 에이전트를 만들면서 문서를 HTML로 보여주고, LLM이 **CSS selector**만 지정해서 필요한 조각을 컨텍스트로 끌어오게 했는데 거의 마법처럼 잘 됨
  - 클라이언트 쪽에서는 **querySelector/querySelectorAll**이 워낙 널리 쓰이니, 이제 PHP의 새 DOM에도 그게 들어온 게 반가움  
    사람들이 익숙한 방식 그대로 쓸 수 있음

- **CSS 문법**과 CSSWG가 정의하는 규칙·함수·단위 같은 전체 체계를 분리해서 부를 이름이 있으면 좋겠음  
  이쪽에 꽤 가능성이 있는데, 다른 활용 사례를 이야기하거나 조사하려면 결국 CSS 파서가 들어간 GitHub 코드를 뒤져보며 사람들이 어떤 기묘한 걸 만드는지 확인하는 수밖에 없어 보임  
  가벼운 노드 기반 마크업 언어와, 템플릿에 무엇이 들어갈지 표현하는 CSS 셀렉터, 그리고 이 조각들을 어떻게 결합할지 제어하는 CSS 비슷한 문법을 섞은 이상한 템플릿 엔진 비슷한 것도 만지작거리고 있음
  - 표준에서는 이미 꽤 명확히 분리돼 있다고 봄  
    [https://www.w3.org/TR/selectors-3/](<https://www.w3.org/TR/selectors-3/>)  
    DOM 스펙도 이걸 참조함  
    [https://dom.spec.whatwg.org/#selectors](<https://dom.spec.whatwg.org/#selectors>)  
    그래서 **CSS selector**라는 통칭은 이미 맞고, 그냥 **selector**라고 불러도 됨  
    **DOM selector**라는 이름이 더 깔끔할 수도 있지만, 정적 CSS에서 쓰는 셀렉터나 JS 엔진 바깥의 다른 DOM 엔진(XML parser, PHP DOM API 등)에서 쓰는 셀렉터까지 생각하면 오히려 더 헷갈릴 수도 있음  
    또 `:hover`나 `::target-text`처럼 브라우저 렌더링·탐색에 직접 묶인 특수 셀렉터도 있음  
    다만 브라우저나 CSS와 덜 결합된 **최소 질의 문법 부분집합**에는 별도 이름이 있으면 유용할 수 있음

- 예전에 컨퍼런스에서 봤던 [https://github.com/braposo/graphql-css](<https://github.com/braposo/graphql-css>)가 떠오름  
  농담 프로젝트였지만, 패턴을 다른 맥락으로 옮겨 심고 다시 쓰는 방식이 의외의 걸 가능하게 한다는 점을 잘 보여줘서 좋았음
  - 이거 재밌네  
    바로 그런 식으로 서로 다른 맥락의 패턴을 가져다 써보려는 중임  
    대부분은 별 데까지 안 가더라도, 해커 감성으로는 꽤 흥미로움

- **pyastgrep**은 [https://pyastgrep.readthedocs.io/en/latest/](<https://pyastgrep.readthedocs.io/en/latest/>)에서 보듯이 Python 문법을 질의할 때 **CSS 셀렉터**를 쓸 수 있음  
  기본값은 XPath고, 예를 들면 `pyastgrep --css 'Call > func > Name#main'`처럼 가능함
  - 이거 정말 좋네  
    내가 가리키고 싶었던 방향과 거의 정확히 맞닿아 있음

- 이게 어떤 시나리오를 해결하는지 잘 모르겠음  
  지금도 자식에 따라 부모를 조건부로 바꿀 수 있음. 예를 들어 `pre`는 기본 패딩이 16px이고, 직접 자식이 `code`면 `&:has(> code)`로 0으로 만들 수 있음
  - 사실 이건 처음엔 서로 다른 두 아이디어가 닮아 보인다는 데서 출발했고, 그 연결을 여러 방향으로 밀어본 쪽에 가까움  
    결론도 "현대 CSS의 한계를 고쳐야 한다"보다는, **CSS 비슷한 문법**을 **Datalog 비슷한 시스템**에 얹으면 트리 형태 데이터를 다루는 일을 더 많은 엔지니어에게 친숙하게 만들 수 있지 않을까에 가까움
  - 여기서 말하는 건 한 번의 스타일 계산으로 해결하는 게 아니라, 선택자에 매칭된 대상의 **기저 데이터 자체를 수정**하는 문법임  
    즉 DOM에 새 자식 요소나 속성을 추가하는 쪽 이야기임

- 지금의 **LLM**은 CSS를 잘 못 다루는 편이라, 오히려 이걸 시도해 보면 LLM이 더 단순하게 추론할 수 있는지 확인해보고 싶어짐

- 실제 쓸모는 잘 떠오르지 않지만 그래도 멋지긴 함

- 음... 이거 그냥 **JQ** 아닌가 싶음

- CSS는 어느 정도 좋아하지만, 점점 **복잡성 creep**이 심해지는 건 싫음  
  프로그래밍 언어가 비프로그래밍 언어보다 더 강력해진다는 논리는 이해하지만, HTML·CSS·JavaScript를 계속 복잡하게 키우느니 차라리 그 전체를 대체하는 다른 무언가가 나오는 편이 낫다고 느낌  
  HTML5의 새 요소들도 대부분 왜 필요한지 잘 모르겠어서 거의 안 씀. 결국 많은 컨테이너는 고유 ID가 붙은 `div`일 뿐이라고 생각하게 됐고, 심지어 내부 링크용 `href` 탐색을 위해 그런 ID에 별칭 같은 게 있었으면 싶었음  
  `[data-theme="dark"] [data-theme="light"] :focus { outline-color: black; }` 같은 것도 머리로 해석하는 데 너무 오래 걸려서 더 이상 우아하고 단순하지 않게 느껴짐  
  반면 `h2 { color: red; }`는 여전히 단순함  
  `ancestor(X, Y) :- parent(X, Y).` 같은 표현도 벌써 생각하기 싫어짐. `:-`는 대체 뭐고, 웃는 얼굴처럼 보임  
  `@container style(--theme: dark) { .card { background: royalblue; color: white; } }`에서 읽기를 멈췄음  
  예전엔 잘 작동하던 표준이 시간이 갈수록 망가지는 것 같아 이상함
  - 내 의도는 **CSS에 문법과 의미를 더 얹자**는 쪽이 아니라, CSS에서 아이디어를 훔쳐와서 논리/관계형 질의 언어와의 유사성을 활용해 **새로운 무언가**를 만들자는 쪽에 더 가까움  
    예를 들어 `[data-theme="dark"] [data-theme="light"] :focus { outline-color: black; }`는 영어식 의사코드로 풀면, `data-theme="dark"`인 X가 있고 그 자식 Y가 `data-theme="light"`이며 포커스 상태면 Y의 `outline-color`를 black으로 하라는 뜻에 가까움  
    그래서 이건 Datalog 식으로 `outline-color(Y, black) if data-theme(X, "dark") and parent(X, Y) and data-theme(Y, "light") and focused(Y)`처럼 쓸 수 있음  
    `:-`를 `if`로, 쉼표를 `and`로 바꾼 셈임  
    더 나아가 `Y.outline_color := black if X.data-theme == dark and Y.parent == X and Y.data-theme == dark and Y.focused`처럼 적어서, `attr(X, val)`을 `X.attr == val` 같은 **UFCS 비슷한 문법 설탕**으로 보이게 만들 수도 있음  
    더 ALGOL 계열처럼 보이게 하려면 `forall Y { Y.outline_color := black if Y.data_theme == "dark" and Y.focused and Y.parent.data_theme == "light" }`처럼도 가능함  
    여기서는 Y를 명시적으로 도입하고 조인 하나는 암묵화해서 더 일반적인 프로그래밍처럼 보이지만, 실제로는 Datalog 엔진이 의존성이 바뀔 때마다 이런 루프를 효율적으로 돌려주는 셈임
